mirror of
https://github.com/github/codeql.git
synced 2025-12-21 11:16:30 +01:00
Merge remote-tracking branch 'upstream/master' into dataflow-indirect-args
Conflicts: cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/defaulttainttracking.cpp cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/tainted.expected cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/test_diff.expected cpp/ql/test/library-tests/dataflow/dataflow-tests/test_ir.expected
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import semmle.code.cpp.pointsto.PointsTo
|
||||
|
||||
/** Holds if there exists a call to a function that might close the file specified by `e`. */
|
||||
predicate closed(Expr e) {
|
||||
fcloseCall(_, e) or
|
||||
exists(ExprCall c |
|
||||
@@ -8,10 +9,19 @@ predicate closed(Expr e) {
|
||||
)
|
||||
}
|
||||
|
||||
/** An expression for which there exists a function call that might close it. */
|
||||
class ClosedExpr extends PointsToExpr {
|
||||
ClosedExpr() { closed(this) }
|
||||
|
||||
override predicate interesting() { closed(this) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `fc` is a call to a function that opens a file that might be closed. For example:
|
||||
* ```
|
||||
* FILE* f = fopen("file.txt", "r");
|
||||
* ...
|
||||
* fclose(f);
|
||||
* ```
|
||||
*/
|
||||
predicate fopenCallMayBeClosed(FunctionCall fc) { fopenCall(fc) and anythingPointsTo(fc) }
|
||||
|
||||
@@ -2,12 +2,24 @@
|
||||
|
||||
import cpp
|
||||
|
||||
/**
|
||||
* An assignment to a variable with the value `0`. For example:
|
||||
* ```
|
||||
* int x;
|
||||
* x = 0;
|
||||
* ```
|
||||
* but not:
|
||||
* ```
|
||||
* int x = 0;
|
||||
* ```
|
||||
*/
|
||||
class ZeroAssignment extends AssignExpr {
|
||||
ZeroAssignment() {
|
||||
this.getAnOperand() instanceof VariableAccess and
|
||||
this.getAnOperand() instanceof Zero
|
||||
}
|
||||
|
||||
/** Gets a variable that is assigned the value `0`. */
|
||||
Variable assignedVariable() { result.getAnAccess() = this.getAnOperand() }
|
||||
}
|
||||
|
||||
|
||||
@@ -4,15 +4,24 @@ private predicate freed(Expr e) {
|
||||
e = any(DeallocationExpr de).getFreedExpr()
|
||||
or
|
||||
exists(ExprCall c |
|
||||
// cautiously assume that any ExprCall could be a freeCall.
|
||||
// cautiously assume that any `ExprCall` could be a deallocation expression.
|
||||
c.getAnArgument() = e
|
||||
)
|
||||
}
|
||||
|
||||
/** An expression that might be deallocated. */
|
||||
class FreedExpr extends PointsToExpr {
|
||||
FreedExpr() { freed(this) }
|
||||
|
||||
override predicate interesting() { freed(this) }
|
||||
}
|
||||
|
||||
/**
|
||||
* An allocation expression that might be deallocated. For example:
|
||||
* ```
|
||||
* int* p = new int;
|
||||
* ...
|
||||
* delete p;
|
||||
* ```
|
||||
*/
|
||||
predicate allocMayBeFreed(AllocationExpr alloc) { anythingPointsTo(alloc) }
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
import cpp
|
||||
|
||||
/**
|
||||
* Holds if `val` is an access to the variable `v`, or if `val`
|
||||
* is an assignment with an access to `v` on the left-hand side.
|
||||
*/
|
||||
predicate valueOfVar(Variable v, Expr val) {
|
||||
val = v.getAnAccess() or
|
||||
val.(AssignExpr).getLValue() = v.getAnAccess()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if either:
|
||||
* - `cond` is an (in)equality expression that compares the variable `v` to the value `-1`, or
|
||||
* - `cond` is a relational expression that compares the variable `v` to a constant.
|
||||
*/
|
||||
predicate boundsCheckExpr(Variable v, Expr cond) {
|
||||
exists(EQExpr eq |
|
||||
cond = eq and
|
||||
@@ -43,6 +52,18 @@ predicate boundsCheckExpr(Variable v, Expr cond) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` is an expression in a conditional statement and `succ` is an
|
||||
* immediate successor of `node` that may be reached after evaluating `node`.
|
||||
* For example, given
|
||||
* ```
|
||||
* if (a < 10 && b) func1();
|
||||
* else func2();
|
||||
* ```
|
||||
* this predicate holds when either:
|
||||
* - `node` is `a < 10` and `succ` is `func2()` or `b`, or
|
||||
* - `node` is `b` and `succ` is `func1()` or `func2()`
|
||||
*/
|
||||
predicate conditionalSuccessor(ControlFlowNode node, ControlFlowNode succ) {
|
||||
if node.isCondition()
|
||||
then succ = node.getATrueSuccessor() or succ = node.getAFalseSuccessor()
|
||||
@@ -52,6 +73,12 @@ predicate conditionalSuccessor(ControlFlowNode node, ControlFlowNode succ) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the current value of the variable `v` at control-flow
|
||||
* node `n` has been used either in:
|
||||
* - an (in)equality comparison with the value `-1`, or
|
||||
* - a relational comparison that compares `v` to a constant.
|
||||
*/
|
||||
predicate boundsChecked(Variable v, ControlFlowNode node) {
|
||||
exists(Expr test |
|
||||
boundsCheckExpr(v, test) and
|
||||
@@ -63,6 +90,14 @@ predicate boundsChecked(Variable v, ControlFlowNode node) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `cond` compares `v` to some common error values. Specifically, this
|
||||
* predicate holds when:
|
||||
* - `cond` checks that `v` is equal to `-1`, or
|
||||
* - `cond` checks that `v` is less than `0`, or
|
||||
* - `cond` checks that `v` is less than or equal to `-1`, or
|
||||
* - `cond` checks that `v` is not some common success value (see `successCondition`).
|
||||
*/
|
||||
predicate errorCondition(Variable v, Expr cond) {
|
||||
exists(EQExpr eq |
|
||||
cond = eq and
|
||||
@@ -88,6 +123,14 @@ predicate errorCondition(Variable v, Expr cond) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `cond` compares `v` to some common success values. Specifically, this
|
||||
* predicate holds when:
|
||||
* - `cond` checks that `v` is not equal to `-1`, or
|
||||
* - `cond` checks that `v` is greater than or equal than `0`, or
|
||||
* - `cond` checks that `v` is greater than `-1`, or
|
||||
* - `cond` checks that `v` is not some common error value (see `errorCondition`).
|
||||
*/
|
||||
predicate successCondition(Variable v, Expr cond) {
|
||||
exists(NEExpr ne |
|
||||
cond = ne and
|
||||
@@ -113,6 +156,11 @@ predicate successCondition(Variable v, Expr cond) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there exists a comparison operation that checks whether `v`
|
||||
* represents some common *error* values, and `n` may be reached
|
||||
* immediately following the comparison operation.
|
||||
*/
|
||||
predicate errorSuccessor(Variable v, ControlFlowNode n) {
|
||||
exists(Expr cond |
|
||||
errorCondition(v, cond) and n = cond.getATrueSuccessor()
|
||||
@@ -121,6 +169,11 @@ predicate errorSuccessor(Variable v, ControlFlowNode n) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there exists a comparison operation that checks whether `v`
|
||||
* represents some common *success* values, and `n` may be reached
|
||||
* immediately following the comparison operation.
|
||||
*/
|
||||
predicate successSuccessor(Variable v, ControlFlowNode n) {
|
||||
exists(Expr cond |
|
||||
successCondition(v, cond) and n = cond.getATrueSuccessor()
|
||||
@@ -129,6 +182,10 @@ predicate successSuccessor(Variable v, ControlFlowNode n) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the current value of the variable `v` at control-flow node
|
||||
* `n` may have been checked against a common set of *error* values.
|
||||
*/
|
||||
predicate checkedError(Variable v, ControlFlowNode n) {
|
||||
errorSuccessor(v, n)
|
||||
or
|
||||
@@ -139,6 +196,10 @@ predicate checkedError(Variable v, ControlFlowNode n) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the current value of the variable `v` at control-flow node
|
||||
* `n` may have been checked against a common set of *success* values.
|
||||
*/
|
||||
predicate checkedSuccess(Variable v, ControlFlowNode n) {
|
||||
successSuccessor(v, n)
|
||||
or
|
||||
|
||||
@@ -5,17 +5,34 @@
|
||||
import cpp
|
||||
import semmle.code.cpp.controlflow.SSA
|
||||
import semmle.code.cpp.dataflow.DataFlow
|
||||
import semmle.code.cpp.models.implementations.Allocation
|
||||
import semmle.code.cpp.models.implementations.Deallocation
|
||||
|
||||
/**
|
||||
* Holds if `alloc` is a use of `malloc` or `new`. `kind` is
|
||||
* a string describing the type of the allocation.
|
||||
*/
|
||||
predicate allocExpr(Expr alloc, string kind) {
|
||||
isAllocationExpr(alloc) and
|
||||
not alloc.isFromUninstantiatedTemplate(_) and
|
||||
(
|
||||
alloc instanceof FunctionCall and
|
||||
kind = "malloc"
|
||||
exists(Function target |
|
||||
alloc.(AllocationExpr).(FunctionCall).getTarget() = target and
|
||||
(
|
||||
target.getName() = "operator new" and
|
||||
kind = "new" and
|
||||
// exclude placement new and custom overloads as they
|
||||
// may not conform to assumptions
|
||||
not target.getNumberOfParameters() > 1
|
||||
or
|
||||
target.getName() = "operator new[]" and
|
||||
kind = "new[]" and
|
||||
// exclude placement new and custom overloads as they
|
||||
// may not conform to assumptions
|
||||
not target.getNumberOfParameters() > 1
|
||||
or
|
||||
not target instanceof OperatorNewAllocationFunction and
|
||||
kind = "malloc"
|
||||
)
|
||||
)
|
||||
or
|
||||
alloc instanceof NewExpr and
|
||||
kind = "new" and
|
||||
@@ -28,7 +45,8 @@ predicate allocExpr(Expr alloc, string kind) {
|
||||
// exclude placement new and custom overloads as they
|
||||
// may not conform to assumptions
|
||||
not alloc.(NewArrayExpr).getAllocatorCall().getTarget().getNumberOfParameters() > 1
|
||||
)
|
||||
) and
|
||||
not alloc.isFromUninstantiatedTemplate(_)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,8 +128,20 @@ predicate allocReaches(Expr e, Expr alloc, string kind) {
|
||||
* describing the type of that free or delete.
|
||||
*/
|
||||
predicate freeExpr(Expr free, Expr freed, string kind) {
|
||||
freeCall(free, freed) and
|
||||
kind = "free"
|
||||
exists(Function target |
|
||||
freed = free.(DeallocationExpr).getFreedExpr() and
|
||||
free.(FunctionCall).getTarget() = target and
|
||||
(
|
||||
target.getName() = "operator delete" and
|
||||
kind = "delete"
|
||||
or
|
||||
target.getName() = "operator delete[]" and
|
||||
kind = "delete[]"
|
||||
or
|
||||
not target instanceof OperatorDeleteDeallocationFunction and
|
||||
kind = "free"
|
||||
)
|
||||
)
|
||||
or
|
||||
free.(DeleteExpr).getExpr() = freed and
|
||||
kind = "delete"
|
||||
|
||||
@@ -30,7 +30,7 @@ predicate allowedTypedefs(TypedefType t) {
|
||||
* Gets a type which appears literally in the declaration of `d`.
|
||||
*/
|
||||
Type getAnImmediateUsedType(Declaration d) {
|
||||
d.isDefined() and
|
||||
d.hasDefinition() and
|
||||
(
|
||||
result = d.(Function).getType() or
|
||||
result = d.(Variable).getType()
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @description Using the TLS or SSLv23 protocol from the boost::asio library, but not disabling deprecated protocols, or disabling minimum-recommended protocols.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @id cpp/boost/tls_settings_misconfiguration
|
||||
* @id cpp/boost/tls-settings-misconfiguration
|
||||
* @tags security
|
||||
*/
|
||||
|
||||
|
||||
@@ -8,6 +8,6 @@ struct S {
|
||||
|
||||
// Whereas here it does make a semantic difference.
|
||||
auto getValCorrect() const -> int {
|
||||
return val
|
||||
return val;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import cpp
|
||||
|
||||
pragma[inline]
|
||||
private predicate arithTypesMatch(Type arg, Type parm) {
|
||||
arg = parm
|
||||
or
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*
|
||||
* By default they fall back to the reasonable defaults provided in
|
||||
* `DefaultOptions.qll`, but by modifying this file, you can customize
|
||||
* the standard Semmle analyses to give better results for your project.
|
||||
* the standard analyses to give better results for your project.
|
||||
*/
|
||||
|
||||
import cpp
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @name Uncontrolled data used in path expression
|
||||
* @description Accessing paths influenced by users can allow an
|
||||
* attacker to access unexpected resources.
|
||||
* @kind problem
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @precision medium
|
||||
* @id cpp/path-injection
|
||||
@@ -17,6 +17,7 @@ import cpp
|
||||
import semmle.code.cpp.security.FunctionWithWrappers
|
||||
import semmle.code.cpp.security.Security
|
||||
import semmle.code.cpp.security.TaintTracking
|
||||
import TaintedWithPath
|
||||
|
||||
/**
|
||||
* A function for opening a file.
|
||||
@@ -51,12 +52,19 @@ class FileFunction extends FunctionWithWrappers {
|
||||
override predicate interestingArg(int arg) { arg = 0 }
|
||||
}
|
||||
|
||||
class TaintedPathConfiguration extends TaintTrackingConfiguration {
|
||||
override predicate isSink(Element tainted) {
|
||||
exists(FileFunction fileFunction | fileFunction.outermostWrapperFunctionCall(tainted, _))
|
||||
}
|
||||
}
|
||||
|
||||
from
|
||||
FileFunction fileFunction, Expr taintedArg, Expr taintSource, string taintCause, string callChain
|
||||
FileFunction fileFunction, Expr taintedArg, Expr taintSource, PathNode sourceNode,
|
||||
PathNode sinkNode, string taintCause, string callChain
|
||||
where
|
||||
fileFunction.outermostWrapperFunctionCall(taintedArg, callChain) and
|
||||
tainted(taintSource, taintedArg) and
|
||||
taintedWithPath(taintSource, taintedArg, sourceNode, sinkNode) and
|
||||
isUserInput(taintSource, taintCause)
|
||||
select taintedArg,
|
||||
select taintedArg, sourceNode, sinkNode,
|
||||
"This argument to a file access function is derived from $@ and then passed to " + callChain,
|
||||
taintSource, "user input (" + taintCause + ")"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @name CGI script vulnerable to cross-site scripting
|
||||
* @description Writing user input directly to a web page
|
||||
* allows for a cross-site scripting vulnerability.
|
||||
* @kind problem
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id cpp/cgi-xss
|
||||
@@ -13,6 +13,7 @@
|
||||
import cpp
|
||||
import semmle.code.cpp.commons.Environment
|
||||
import semmle.code.cpp.security.TaintTracking
|
||||
import TaintedWithPath
|
||||
|
||||
/** A call that prints its arguments to `stdout`. */
|
||||
class PrintStdoutCall extends FunctionCall {
|
||||
@@ -27,8 +28,13 @@ class QueryString extends EnvironmentRead {
|
||||
QueryString() { getEnvironmentVariable() = "QUERY_STRING" }
|
||||
}
|
||||
|
||||
from QueryString query, PrintStdoutCall call, Element printedArg
|
||||
where
|
||||
call.getAnArgument() = printedArg and
|
||||
tainted(query, printedArg)
|
||||
select printedArg, "Cross-site scripting vulnerability due to $@.", query, "this query data"
|
||||
class Configuration extends TaintTrackingConfiguration {
|
||||
override predicate isSink(Element tainted) {
|
||||
exists(PrintStdoutCall call | call.getAnArgument() = tainted)
|
||||
}
|
||||
}
|
||||
|
||||
from QueryString query, Element printedArg, PathNode sourceNode, PathNode sinkNode
|
||||
where taintedWithPath(query, printedArg, sourceNode, sinkNode)
|
||||
select printedArg, sourceNode, sinkNode, "Cross-site scripting vulnerability due to $@.", query,
|
||||
"this query data"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @description Including user-supplied data in a SQL query without
|
||||
* neutralizing special elements can make code vulnerable
|
||||
* to SQL Injection.
|
||||
* @kind problem
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id cpp/sql-injection
|
||||
@@ -15,6 +15,7 @@ import cpp
|
||||
import semmle.code.cpp.security.Security
|
||||
import semmle.code.cpp.security.FunctionWithWrappers
|
||||
import semmle.code.cpp.security.TaintTracking
|
||||
import TaintedWithPath
|
||||
|
||||
class SQLLikeFunction extends FunctionWithWrappers {
|
||||
SQLLikeFunction() { sqlArgument(this.getName(), _) }
|
||||
@@ -22,11 +23,19 @@ class SQLLikeFunction extends FunctionWithWrappers {
|
||||
override predicate interestingArg(int arg) { sqlArgument(this.getName(), arg) }
|
||||
}
|
||||
|
||||
from SQLLikeFunction runSql, Expr taintedArg, Expr taintSource, string taintCause, string callChain
|
||||
class Configuration extends TaintTrackingConfiguration {
|
||||
override predicate isSink(Element tainted) {
|
||||
exists(SQLLikeFunction runSql | runSql.outermostWrapperFunctionCall(tainted, _))
|
||||
}
|
||||
}
|
||||
|
||||
from
|
||||
SQLLikeFunction runSql, Expr taintedArg, Expr taintSource, PathNode sourceNode, PathNode sinkNode,
|
||||
string taintCause, string callChain
|
||||
where
|
||||
runSql.outermostWrapperFunctionCall(taintedArg, callChain) and
|
||||
tainted(taintSource, taintedArg) and
|
||||
taintedWithPath(taintSource, taintedArg, sourceNode, sinkNode) and
|
||||
isUserInput(taintSource, taintCause)
|
||||
select taintedArg,
|
||||
select taintedArg, sourceNode, sinkNode,
|
||||
"This argument to a SQL query function is derived from $@ and then passed to " + callChain,
|
||||
taintSource, "user input (" + taintCause + ")"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @description Using externally controlled strings in a process
|
||||
* operation can allow an attacker to execute malicious
|
||||
* commands.
|
||||
* @kind problem
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @precision medium
|
||||
* @id cpp/uncontrolled-process-operation
|
||||
@@ -14,13 +14,24 @@
|
||||
import cpp
|
||||
import semmle.code.cpp.security.Security
|
||||
import semmle.code.cpp.security.TaintTracking
|
||||
import TaintedWithPath
|
||||
|
||||
from string processOperation, int processOperationArg, FunctionCall call, Expr arg, Element source
|
||||
predicate isProcessOperationExplanation(Expr arg, string processOperation) {
|
||||
exists(int processOperationArg, FunctionCall call |
|
||||
isProcessOperationArgument(processOperation, processOperationArg) and
|
||||
call.getTarget().getName() = processOperation and
|
||||
call.getArgument(processOperationArg) = arg
|
||||
)
|
||||
}
|
||||
|
||||
class Configuration extends TaintTrackingConfiguration {
|
||||
override predicate isSink(Element arg) { isProcessOperationExplanation(arg, _) }
|
||||
}
|
||||
|
||||
from string processOperation, Expr arg, Expr source, PathNode sourceNode, PathNode sinkNode
|
||||
where
|
||||
isProcessOperationArgument(processOperation, processOperationArg) and
|
||||
call.getTarget().getName() = processOperation and
|
||||
call.getArgument(processOperationArg) = arg and
|
||||
tainted(source, arg)
|
||||
select arg,
|
||||
isProcessOperationExplanation(arg, processOperation) and
|
||||
taintedWithPath(source, arg, sourceNode, sinkNode)
|
||||
select arg, sourceNode, sinkNode,
|
||||
"The value of this argument may come from $@ and is being passed to " + processOperation, source,
|
||||
source.toString()
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @name Unbounded write
|
||||
* @description Buffer write operations that do not control the length
|
||||
* of data written may overflow.
|
||||
* @kind problem
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision medium
|
||||
* @id cpp/unbounded-write
|
||||
@@ -16,6 +16,7 @@
|
||||
import semmle.code.cpp.security.BufferWrite
|
||||
import semmle.code.cpp.security.Security
|
||||
import semmle.code.cpp.security.TaintTracking
|
||||
import TaintedWithPath
|
||||
|
||||
/*
|
||||
* --- Summary of CWE-120 alerts ---
|
||||
@@ -54,32 +55,48 @@ predicate isUnboundedWrite(BufferWrite bw) {
|
||||
* }
|
||||
*/
|
||||
|
||||
/**
|
||||
* Holds if `e` is a source buffer going into an unbounded write `bw` or a
|
||||
* qualifier of (a qualifier of ...) such a source.
|
||||
*/
|
||||
predicate unboundedWriteSource(Expr e, BufferWrite bw) {
|
||||
isUnboundedWrite(bw) and e = bw.getASource()
|
||||
or
|
||||
exists(FieldAccess fa | unboundedWriteSource(fa, bw) and e = fa.getQualifier())
|
||||
}
|
||||
|
||||
/*
|
||||
* --- user input reach ---
|
||||
*/
|
||||
|
||||
/**
|
||||
* Identifies expressions that are potentially tainted with user
|
||||
* input. Most of the work for this is actually done by the
|
||||
* TaintTracking library.
|
||||
*/
|
||||
predicate tainted2(Expr expr, Expr inputSource, string inputCause) {
|
||||
taintedIncludingGlobalVars(inputSource, expr, _) and
|
||||
inputCause = inputSource.toString()
|
||||
or
|
||||
exists(Expr e | tainted2(e, inputSource, inputCause) |
|
||||
// field accesses of a tainted struct are tainted
|
||||
e = expr.(FieldAccess).getQualifier()
|
||||
)
|
||||
class Configuration extends TaintTrackingConfiguration {
|
||||
override predicate isSink(Element tainted) { unboundedWriteSource(tainted, _) }
|
||||
|
||||
override predicate taintThroughGlobals() { any() }
|
||||
}
|
||||
|
||||
/*
|
||||
* --- put it together ---
|
||||
*/
|
||||
|
||||
from BufferWrite bw, Expr inputSource, string inputCause
|
||||
/*
|
||||
* An unbounded write is, for example `strcpy(..., tainted)`. We're looking
|
||||
* for a tainted source buffer of an unbounded write, where this source buffer
|
||||
* is a sink in the taint-tracking analysis.
|
||||
*
|
||||
* In the case of `gets` and `scanf`, where the source buffer is implicit, the
|
||||
* `BufferWrite` library reports the source buffer to be the same as the
|
||||
* destination buffer. Since those destination-buffer arguments are also
|
||||
* modeled in the taint-tracking library as being _sources_ of taint, they are
|
||||
* in practice reported as being tainted because the `security.TaintTracking`
|
||||
* library does not distinguish between taint going into an argument and out of
|
||||
* an argument. Thus, we get the desired alerts.
|
||||
*/
|
||||
|
||||
from BufferWrite bw, Expr inputSource, Expr tainted, PathNode sourceNode, PathNode sinkNode
|
||||
where
|
||||
isUnboundedWrite(bw) and
|
||||
tainted2(bw.getASource(), inputSource, inputCause)
|
||||
select bw, "This '" + bw.getBWDesc() + "' with input from $@ may overflow the destination.",
|
||||
inputSource, inputCause
|
||||
taintedWithPath(inputSource, tainted, sourceNode, sinkNode) and
|
||||
unboundedWriteSource(tainted, bw)
|
||||
select bw, sourceNode, sinkNode,
|
||||
"This '" + bw.getBWDesc() + "' with input from $@ may overflow the destination.", inputSource,
|
||||
inputSource.toString()
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @description Using externally-controlled format strings in
|
||||
* printf-style functions can lead to buffer overflows
|
||||
* or data representation problems.
|
||||
* @kind problem
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @precision medium
|
||||
* @id cpp/tainted-format-string
|
||||
@@ -16,12 +16,21 @@ import cpp
|
||||
import semmle.code.cpp.security.Security
|
||||
import semmle.code.cpp.security.FunctionWithWrappers
|
||||
import semmle.code.cpp.security.TaintTracking
|
||||
import TaintedWithPath
|
||||
|
||||
from PrintfLikeFunction printf, Expr arg, string printfFunction, Expr userValue, string cause
|
||||
class Configuration extends TaintTrackingConfiguration {
|
||||
override predicate isSink(Element tainted) {
|
||||
exists(PrintfLikeFunction printf | printf.outermostWrapperFunctionCall(tainted, _))
|
||||
}
|
||||
}
|
||||
|
||||
from
|
||||
PrintfLikeFunction printf, Expr arg, PathNode sourceNode, PathNode sinkNode,
|
||||
string printfFunction, Expr userValue, string cause
|
||||
where
|
||||
printf.outermostWrapperFunctionCall(arg, printfFunction) and
|
||||
tainted(userValue, arg) and
|
||||
taintedWithPath(userValue, arg, sourceNode, sinkNode) and
|
||||
isUserInput(userValue, cause)
|
||||
select arg,
|
||||
select arg, sourceNode, sinkNode,
|
||||
"The value of this argument may come from $@ and is being used as a formatting argument to " +
|
||||
printfFunction, userValue, cause
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @description Using externally-controlled format strings in
|
||||
* printf-style functions can lead to buffer overflows
|
||||
* or data representation problems.
|
||||
* @kind problem
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @precision medium
|
||||
* @id cpp/tainted-format-string-through-global
|
||||
@@ -16,15 +16,24 @@ import cpp
|
||||
import semmle.code.cpp.security.FunctionWithWrappers
|
||||
import semmle.code.cpp.security.Security
|
||||
import semmle.code.cpp.security.TaintTracking
|
||||
import TaintedWithPath
|
||||
|
||||
class Configuration extends TaintTrackingConfiguration {
|
||||
override predicate isSink(Element tainted) {
|
||||
exists(PrintfLikeFunction printf | printf.outermostWrapperFunctionCall(tainted, _))
|
||||
}
|
||||
|
||||
override predicate taintThroughGlobals() { any() }
|
||||
}
|
||||
|
||||
from
|
||||
PrintfLikeFunction printf, Expr arg, string printfFunction, Expr userValue, string cause,
|
||||
string globalVar
|
||||
PrintfLikeFunction printf, Expr arg, PathNode sourceNode, PathNode sinkNode,
|
||||
string printfFunction, Expr userValue, string cause
|
||||
where
|
||||
printf.outermostWrapperFunctionCall(arg, printfFunction) and
|
||||
not tainted(_, arg) and
|
||||
taintedIncludingGlobalVars(userValue, arg, globalVar) and
|
||||
not taintedWithoutGlobals(arg) and
|
||||
taintedWithPath(userValue, arg, sourceNode, sinkNode) and
|
||||
isUserInput(userValue, cause)
|
||||
select arg,
|
||||
"This value may flow through $@, originating from $@, and is a formatting argument to " +
|
||||
printfFunction + ".", globalVarFromId(globalVar), globalVar, userValue, cause
|
||||
select arg, sourceNode, sinkNode,
|
||||
"The value of this argument may come from $@ and is being used as a formatting argument to " +
|
||||
printfFunction, userValue, cause
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @name Uncontrolled data in arithmetic expression
|
||||
* @description Arithmetic operations on uncontrolled data that is not
|
||||
* validated can cause overflows.
|
||||
* @kind problem
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @precision medium
|
||||
* @id cpp/uncontrolled-arithmetic
|
||||
@@ -15,6 +15,7 @@ import cpp
|
||||
import semmle.code.cpp.security.Overflow
|
||||
import semmle.code.cpp.security.Security
|
||||
import semmle.code.cpp.security.TaintTracking
|
||||
import TaintedWithPath
|
||||
|
||||
predicate isRandCall(FunctionCall fc) { fc.getTarget().getName() = "rand" }
|
||||
|
||||
@@ -40,9 +41,22 @@ class SecurityOptionsArith extends SecurityOptions {
|
||||
}
|
||||
}
|
||||
|
||||
predicate taintedVarAccess(Expr origin, VariableAccess va) {
|
||||
isUserInput(origin, _) and
|
||||
tainted(origin, va)
|
||||
predicate isDiv(VariableAccess va) { exists(AssignDivExpr div | div.getLValue() = va) }
|
||||
|
||||
predicate missingGuard(VariableAccess va, string effect) {
|
||||
exists(Operation op | op.getAnOperand() = va |
|
||||
missingGuardAgainstUnderflow(op, va) and effect = "underflow"
|
||||
or
|
||||
missingGuardAgainstOverflow(op, va) and effect = "overflow"
|
||||
)
|
||||
}
|
||||
|
||||
class Configuration extends TaintTrackingConfiguration {
|
||||
override predicate isSink(Element e) {
|
||||
isDiv(e)
|
||||
or
|
||||
missingGuard(e, _)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,19 +64,17 @@ predicate taintedVarAccess(Expr origin, VariableAccess va) {
|
||||
* range.
|
||||
*/
|
||||
predicate guardedByAssignDiv(Expr origin) {
|
||||
isUserInput(origin, _) and
|
||||
exists(AssignDivExpr div, VariableAccess va | tainted(origin, va) and div.getLValue() = va)
|
||||
exists(VariableAccess va |
|
||||
taintedWithPath(origin, va, _, _) and
|
||||
isDiv(va)
|
||||
)
|
||||
}
|
||||
|
||||
from Expr origin, Operation op, VariableAccess va, string effect
|
||||
from Expr origin, VariableAccess va, string effect, PathNode sourceNode, PathNode sinkNode
|
||||
where
|
||||
taintedVarAccess(origin, va) and
|
||||
op.getAnOperand() = va and
|
||||
(
|
||||
missingGuardAgainstUnderflow(op, va) and effect = "underflow"
|
||||
or
|
||||
missingGuardAgainstOverflow(op, va) and effect = "overflow"
|
||||
) and
|
||||
taintedWithPath(origin, va, sourceNode, sinkNode) and
|
||||
missingGuard(va, effect) and
|
||||
not guardedByAssignDiv(origin)
|
||||
select va, "$@ flows to here and is used in arithmetic, potentially causing an " + effect + ".",
|
||||
origin, "Uncontrolled value"
|
||||
select va, sourceNode, sinkNode,
|
||||
"$@ flows to here and is used in arithmetic, potentially causing an " + effect + ".", origin,
|
||||
"Uncontrolled value"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @name Overflow in uncontrolled allocation size
|
||||
* @description Allocating memory with a size controlled by an external
|
||||
* user can result in integer overflow.
|
||||
* @kind problem
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id cpp/uncontrolled-allocation-size
|
||||
@@ -13,21 +13,33 @@
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.security.TaintTracking
|
||||
import TaintedWithPath
|
||||
|
||||
predicate taintedAllocSize(Expr e, Expr source, string taintCause) {
|
||||
(
|
||||
isAllocationExpr(e) or
|
||||
any(MulExpr me | me.getAChild() instanceof SizeofOperator) = e
|
||||
) and
|
||||
/**
|
||||
* Holds if `alloc` is an allocation, and `tainted` is a child of it that is a
|
||||
* taint sink.
|
||||
*/
|
||||
predicate allocSink(Expr alloc, Expr tainted) {
|
||||
isAllocationExpr(alloc) and
|
||||
tainted = alloc.getAChild() and
|
||||
tainted.getUnspecifiedType() instanceof IntegralType
|
||||
}
|
||||
|
||||
class TaintedAllocationSizeConfiguration extends TaintTrackingConfiguration {
|
||||
override predicate isSink(Element tainted) { allocSink(_, tainted) }
|
||||
}
|
||||
|
||||
predicate taintedAllocSize(
|
||||
Expr source, Expr alloc, PathNode sourceNode, PathNode sinkNode, string taintCause
|
||||
) {
|
||||
isUserInput(source, taintCause) and
|
||||
exists(Expr tainted |
|
||||
tainted = e.getAChild() and
|
||||
tainted.getUnspecifiedType() instanceof IntegralType and
|
||||
isUserInput(source, taintCause) and
|
||||
tainted(source, tainted)
|
||||
allocSink(alloc, tainted) and
|
||||
taintedWithPath(source, tainted, sourceNode, sinkNode)
|
||||
)
|
||||
}
|
||||
|
||||
from Expr e, Expr source, string taintCause
|
||||
where taintedAllocSize(e, source, taintCause)
|
||||
select e, "This allocation size is derived from $@ and might overflow", source,
|
||||
"user input (" + taintCause + ")"
|
||||
from Expr source, Expr alloc, PathNode sourceNode, PathNode sinkNode, string taintCause
|
||||
where taintedAllocSize(source, alloc, sourceNode, sinkNode, taintCause)
|
||||
select alloc, sourceNode, sinkNode, "This allocation size is derived from $@ and might overflow",
|
||||
source, "user input (" + taintCause + ")"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @description Authentication by checking that the peer's address
|
||||
* matches a known IP or web address is unsafe as it is
|
||||
* vulnerable to spoofing attacks.
|
||||
* @kind problem
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @precision medium
|
||||
* @id cpp/user-controlled-bypass
|
||||
@@ -12,6 +12,7 @@
|
||||
*/
|
||||
|
||||
import semmle.code.cpp.security.TaintTracking
|
||||
import TaintedWithPath
|
||||
|
||||
predicate hardCodedAddressOrIP(StringLiteral txt) {
|
||||
exists(string s | s = txt.getValueText() |
|
||||
@@ -102,16 +103,21 @@ predicate useOfHardCodedAddressOrIP(Expr use) {
|
||||
* untrusted input then it might be vulnerable to a spoofing
|
||||
* attack.
|
||||
*/
|
||||
predicate hardCodedAddressInCondition(Expr source, Expr condition) {
|
||||
// One of the sub-expressions of the condition is tainted.
|
||||
exists(Expr taintedExpr | taintedExpr.getParent+() = condition | tainted(source, taintedExpr)) and
|
||||
predicate hardCodedAddressInCondition(Expr subexpression, Expr condition) {
|
||||
subexpression = condition.getAChild+() and
|
||||
// One of the sub-expressions of the condition is a hard-coded
|
||||
// IP or web-address.
|
||||
exists(Expr use | use.getParent+() = condition | useOfHardCodedAddressOrIP(use)) and
|
||||
exists(Expr use | use = condition.getAChild+() | useOfHardCodedAddressOrIP(use)) and
|
||||
condition = any(IfStmt ifStmt).getCondition()
|
||||
}
|
||||
|
||||
from Expr source, Expr condition
|
||||
where hardCodedAddressInCondition(source, condition)
|
||||
select condition, "Untrusted input $@ might be vulnerable to a spoofing attack.", source,
|
||||
source.toString()
|
||||
class Configuration extends TaintTrackingConfiguration {
|
||||
override predicate isSink(Element sink) { hardCodedAddressInCondition(sink, _) }
|
||||
}
|
||||
|
||||
from Expr subexpression, Expr source, Expr condition, PathNode sourceNode, PathNode sinkNode
|
||||
where
|
||||
hardCodedAddressInCondition(subexpression, condition) and
|
||||
taintedWithPath(source, subexpression, sourceNode, sinkNode)
|
||||
select condition, sourceNode, sinkNode,
|
||||
"Untrusted input $@ might be vulnerable to a spoofing attack.", source, source.toString()
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @name Cleartext storage of sensitive information in buffer
|
||||
* @description Storing sensitive information in cleartext can expose it
|
||||
* to an attacker.
|
||||
* @kind problem
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @precision medium
|
||||
* @id cpp/cleartext-storage-buffer
|
||||
@@ -14,12 +14,20 @@ import cpp
|
||||
import semmle.code.cpp.security.BufferWrite
|
||||
import semmle.code.cpp.security.TaintTracking
|
||||
import semmle.code.cpp.security.SensitiveExprs
|
||||
import TaintedWithPath
|
||||
|
||||
from BufferWrite w, Expr taintedArg, Expr taintSource, string taintCause, SensitiveExpr dest
|
||||
class Configuration extends TaintTrackingConfiguration {
|
||||
override predicate isSink(Element tainted) { exists(BufferWrite w | w.getASource() = tainted) }
|
||||
}
|
||||
|
||||
from
|
||||
BufferWrite w, Expr taintedArg, Expr taintSource, PathNode sourceNode, PathNode sinkNode,
|
||||
string taintCause, SensitiveExpr dest
|
||||
where
|
||||
tainted(taintSource, taintedArg) and
|
||||
taintedWithPath(taintSource, taintedArg, sourceNode, sinkNode) and
|
||||
isUserInput(taintSource, taintCause) and
|
||||
w.getASource() = taintedArg and
|
||||
dest = w.getDest()
|
||||
select w, "This write into buffer '" + dest.toString() + "' may contain unencrypted data from $@",
|
||||
select w, sourceNode, sinkNode,
|
||||
"This write into buffer '" + dest.toString() + "' may contain unencrypted data from $@",
|
||||
taintSource, "user input (" + taintCause + ")"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @name Cleartext storage of sensitive information in an SQLite database
|
||||
* @description Storing sensitive information in a non-encrypted
|
||||
* database can expose it to an attacker.
|
||||
* @kind problem
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @precision medium
|
||||
* @id cpp/cleartext-storage-database
|
||||
@@ -13,6 +13,7 @@
|
||||
import cpp
|
||||
import semmle.code.cpp.security.SensitiveExprs
|
||||
import semmle.code.cpp.security.TaintTracking
|
||||
import TaintedWithPath
|
||||
|
||||
class UserInputIsSensitiveExpr extends SecurityOptions {
|
||||
override predicate isUserInput(Expr expr, string cause) {
|
||||
@@ -32,10 +33,21 @@ predicate sqlite_encryption_used() {
|
||||
any(FunctionCall fc).getTarget().getName().matches("sqlite%\\_key\\_%")
|
||||
}
|
||||
|
||||
from SensitiveExpr taintSource, Expr taintedArg, SqliteFunctionCall sqliteCall
|
||||
class Configuration extends TaintTrackingConfiguration {
|
||||
override predicate isSink(Element taintedArg) {
|
||||
exists(SqliteFunctionCall sqliteCall |
|
||||
taintedArg = sqliteCall.getASource() and
|
||||
not sqlite_encryption_used()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from
|
||||
SensitiveExpr taintSource, Expr taintedArg, SqliteFunctionCall sqliteCall, PathNode sourceNode,
|
||||
PathNode sinkNode
|
||||
where
|
||||
tainted(taintSource, taintedArg) and
|
||||
taintedArg = sqliteCall.getASource() and
|
||||
not sqlite_encryption_used()
|
||||
select sqliteCall, "This SQLite call may store $@ in a non-encrypted SQLite database", taintSource,
|
||||
taintedWithPath(taintSource, taintedArg, sourceNode, sinkNode) and
|
||||
taintedArg = sqliteCall.getASource()
|
||||
select sqliteCall, sourceNode, sinkNode,
|
||||
"This SQLite call may store $@ in a non-encrypted SQLite database", taintSource,
|
||||
"sensitive information"
|
||||
|
||||
@@ -198,12 +198,12 @@ class InitializationFunction extends Function {
|
||||
)
|
||||
or
|
||||
// If we have no definition, we look at SAL annotations
|
||||
not this.isDefined() and
|
||||
not this.hasDefinition() and
|
||||
this.getParameter(i).(SALParameter).isOut() and
|
||||
evidence = SuggestiveSALAnnotation()
|
||||
or
|
||||
// We have some external information that this function conditionally initializes
|
||||
not this.isDefined() and
|
||||
not this.hasDefinition() and
|
||||
any(ValidatedExternalCondInitFunction vc).isExternallyVerified(this, i) and
|
||||
evidence = ExternalEvidence()
|
||||
}
|
||||
@@ -406,7 +406,7 @@ class ConditionalInitializationFunction extends InitializationFunction {
|
||||
* Explicitly ignore pure virtual functions.
|
||||
*/
|
||||
|
||||
this.isDefined() and
|
||||
this.hasDefinition() and
|
||||
this.paramNotReassignedAt(this, i, c) and
|
||||
not this instanceof PureVirtualFunction
|
||||
)
|
||||
@@ -616,11 +616,11 @@ private predicate functionSignature(Function f, string qualifiedName, string typ
|
||||
* are never statically linked together.
|
||||
*/
|
||||
private Function getAPossibleDefinition(Function undefinedFunction) {
|
||||
not undefinedFunction.isDefined() and
|
||||
not undefinedFunction.hasDefinition() and
|
||||
exists(string qn, string typeSig |
|
||||
functionSignature(undefinedFunction, qn, typeSig) and functionSignature(result, qn, typeSig)
|
||||
) and
|
||||
result.isDefined()
|
||||
result.hasDefinition()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -631,7 +631,7 @@ private Function getAPossibleDefinition(Function undefinedFunction) {
|
||||
*/
|
||||
private Function getTarget1(Call c) {
|
||||
result = VirtualDispatch::getAViableTarget(c) and
|
||||
result.isDefined()
|
||||
result.hasDefinition()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,7 +21,7 @@ where
|
||||
destBase = baseType(destType) and
|
||||
destBase.getSize() != sourceBase.getSize() and
|
||||
not dest.isInMacroExpansion() and
|
||||
// If the source type is a char* or void* then don't
|
||||
// If the source type is a `char*` or `void*` then don't
|
||||
// produce a result, because it is likely to be a false
|
||||
// positive.
|
||||
not sourceBase instanceof CharType and
|
||||
|
||||
@@ -21,7 +21,7 @@ where
|
||||
destBase = baseType(destType) and
|
||||
destBase.getSize() != sourceBase.getSize() and
|
||||
not dest.isInMacroExpansion() and
|
||||
// If the source type is a char* or void* then don't
|
||||
// If the source type is a `char*` or `void*` then don't
|
||||
// produce a result, because it is likely to be a false
|
||||
// positive.
|
||||
not sourceBase instanceof CharType and
|
||||
|
||||
@@ -24,9 +24,9 @@ private predicate isCharSzPtrExpr(Expr e) {
|
||||
from Expr sizeofExpr, Expr e
|
||||
where
|
||||
// If we see an addWithSizeof then we expect the type of
|
||||
// the pointer expression to be char* or void*. Otherwise it
|
||||
// the pointer expression to be `char*` or `void*`. Otherwise it
|
||||
// is probably a mistake.
|
||||
addWithSizeof(e, sizeofExpr, _) and not isCharSzPtrExpr(e)
|
||||
select sizeofExpr,
|
||||
"Suspicious sizeof offset in a pointer arithmetic expression. " + "The type of the pointer is " +
|
||||
e.getFullyConverted().getType().toString() + "."
|
||||
"Suspicious sizeof offset in a pointer arithmetic expression. The type of the pointer is $@.",
|
||||
e.getFullyConverted().getType() as t, t.toString()
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @description Using untrusted inputs in a statement that makes a
|
||||
* security decision makes code vulnerable to
|
||||
* attack.
|
||||
* @kind problem
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @precision medium
|
||||
* @id cpp/tainted-permissions-check
|
||||
@@ -12,14 +12,9 @@
|
||||
*/
|
||||
|
||||
import semmle.code.cpp.security.TaintTracking
|
||||
import TaintedWithPath
|
||||
|
||||
/**
|
||||
* Holds if there is an 'if' statement whose condition `condition`
|
||||
* is influenced by tainted data `source`, and the body contains
|
||||
* `raise` which escalates privilege.
|
||||
*/
|
||||
predicate cwe807violation(Expr source, Expr condition, Expr raise) {
|
||||
tainted(source, condition) and
|
||||
predicate sensitiveCondition(Expr condition, Expr raise) {
|
||||
raisesPrivilege(raise) and
|
||||
exists(IfStmt ifstmt |
|
||||
ifstmt.getCondition() = condition and
|
||||
@@ -27,7 +22,19 @@ predicate cwe807violation(Expr source, Expr condition, Expr raise) {
|
||||
)
|
||||
}
|
||||
|
||||
from Expr source, Expr condition, Expr raise
|
||||
where cwe807violation(source, condition, raise)
|
||||
select condition, "Reliance on untrusted input $@ to raise privilege at $@", source,
|
||||
source.toString(), raise, raise.toString()
|
||||
class Configuration extends TaintTrackingConfiguration {
|
||||
override predicate isSink(Element tainted) { sensitiveCondition(tainted, _) }
|
||||
}
|
||||
|
||||
/*
|
||||
* Produce an alert if there is an 'if' statement whose condition `condition`
|
||||
* is influenced by tainted data `source`, and the body contains
|
||||
* `raise` which escalates privilege.
|
||||
*/
|
||||
|
||||
from Expr source, Expr condition, Expr raise, PathNode sourceNode, PathNode sinkNode
|
||||
where
|
||||
taintedWithPath(source, condition, sourceNode, sinkNode) and
|
||||
sensitiveCondition(condition, raise)
|
||||
select condition, sourceNode, sinkNode, "Reliance on untrusted input $@ to raise privilege at $@",
|
||||
source, source.toString(), raise, raise.toString()
|
||||
|
||||
@@ -12,3 +12,8 @@
|
||||
- Critical/FileNeverClosed.ql
|
||||
- Critical/MemoryMayNotBeFreed.ql
|
||||
- Critical/MemoryNeverFreed.ql
|
||||
# These are only for IDE use.
|
||||
- exclude:
|
||||
tags contain:
|
||||
- ide-contextual-queries/local-definitions
|
||||
- ide-contextual-queries/local-references
|
||||
|
||||
@@ -132,6 +132,7 @@ private predicate constructorCallTypeMention(ConstructorCall cc, TypeMention tm)
|
||||
* - `"X"` for macro accesses
|
||||
* - `"I"` for import / include directives
|
||||
*/
|
||||
cached
|
||||
Top definitionOf(Top e, string kind) {
|
||||
(
|
||||
// call -> function called
|
||||
@@ -213,3 +214,11 @@ Top definitionOf(Top e, string kind) {
|
||||
// later on.
|
||||
strictcount(result.getLocation()) < 10
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an appropriately encoded version of a filename `name`
|
||||
* passed by the VS Code extension in order to coincide with the
|
||||
* output of `.getFile()` on locatable entities.
|
||||
*/
|
||||
cached
|
||||
File getEncodedFile(string name) { result.getAbsolutePath().replaceAll(":", "_") = name }
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
/**
|
||||
* Provides precise tracking of how big the memory pointed to by pointers is.
|
||||
* For each pointer, we start tracking (starting from the allocation or an array declaration)
|
||||
* 1) how long is the chunk of memory allocated
|
||||
* 2) where the current pointer is in this chunk of memory
|
||||
* As computing this information is obviously not possible for all pointers,
|
||||
* we do not guarantee the existence of length/offset information for all pointers.
|
||||
* However, when it exists it is guaranteed to be accurate.
|
||||
*
|
||||
* The length and offset are tracked in a similar way to the Rangeanalysis.
|
||||
* Each length is a `ValueNumber + delta`, and each Offset is an `Operand + delta`.
|
||||
* We choose to track a `ValueNumber` for length, because the Rangeanalysis offers
|
||||
* integer bounds on instructions and operands in terms of `ValueNumber`s,
|
||||
* and `Operand` for offset because integer bounds on `Operand`s are
|
||||
* tighter than bounds on `Instruction`s.
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.ir.IR
|
||||
private import semmle.code.cpp.ir.ValueNumbering
|
||||
private import semmle.code.cpp.ir.internal.CppType
|
||||
private import semmle.code.cpp.models.interfaces.Allocation
|
||||
private import semmle.code.cpp.rangeanalysis.RangeUtils
|
||||
|
||||
private newtype TLength =
|
||||
TZeroLength() or
|
||||
TVNLength(ValueNumber vn) {
|
||||
not vn.getAnInstruction() instanceof ConstantInstruction and
|
||||
exists(Instruction i |
|
||||
vn.getAnInstruction() = i and
|
||||
(
|
||||
i.getResultIRType() instanceof IRSignedIntegerType or
|
||||
i.getResultIRType() instanceof IRUnsignedIntegerType
|
||||
)
|
||||
|
|
||||
i instanceof PhiInstruction
|
||||
or
|
||||
i instanceof InitializeParameterInstruction
|
||||
or
|
||||
i instanceof CallInstruction
|
||||
or
|
||||
i.(LoadInstruction).getSourceAddress() instanceof VariableAddressInstruction
|
||||
or
|
||||
i.(LoadInstruction).getSourceAddress() instanceof FieldAddressInstruction
|
||||
or
|
||||
i.getAUse() instanceof ArgumentOperand
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Array lengths are represented in a ValueNumber | Zero + delta format.
|
||||
* This class keeps track of the ValueNumber or Zero.
|
||||
* The delta is tracked in the predicate `knownArrayLength`.
|
||||
*/
|
||||
class Length extends TLength {
|
||||
string toString() { none() } // overridden in subclasses
|
||||
}
|
||||
|
||||
/**
|
||||
* This length class corresponds to an array having a constant length
|
||||
* that is tracked by the delta value.
|
||||
*/
|
||||
class ZeroLength extends Length, TZeroLength {
|
||||
override string toString() { result = "ZeroLength" }
|
||||
}
|
||||
|
||||
/**
|
||||
* This length class corresponds to an array having variable length, i.e. the
|
||||
* length is tracked by a value number. One example is an array having length
|
||||
* `count` for an integer variable `count` in the program.
|
||||
*/
|
||||
class VNLength extends Length, TVNLength {
|
||||
ValueNumber vn;
|
||||
|
||||
VNLength() { this = TVNLength(vn) }
|
||||
|
||||
/** Gets an instruction with this value number bound. */
|
||||
Instruction getInstruction() { this = TVNLength(valueNumber(result)) }
|
||||
|
||||
ValueNumber getValueNumber() { result = vn }
|
||||
|
||||
override string toString() { result = "VNLength(" + vn.getExampleInstruction().toString() + ")" }
|
||||
}
|
||||
|
||||
private newtype TOffset =
|
||||
TZeroOffset() or
|
||||
TOpOffset(Operand op) {
|
||||
op.getAnyDef().getResultIRType() instanceof IRSignedIntegerType or
|
||||
op.getAnyDef().getResultIRType() instanceof IRUnsignedIntegerType
|
||||
}
|
||||
|
||||
/**
|
||||
* This class describes the offset of a pointer in a chunk of memory.
|
||||
* It is either an `Operand` or zero, an additional integer delta is added later.
|
||||
*/
|
||||
class Offset extends TOffset {
|
||||
string toString() { none() } // overridden in subclasses
|
||||
}
|
||||
|
||||
/**
|
||||
* This class represents a fixed offset, only specified by a delta.
|
||||
*/
|
||||
class ZeroOffset extends Offset, TZeroOffset {
|
||||
override string toString() { result = "ZeroOffset" }
|
||||
}
|
||||
|
||||
/**
|
||||
* This class represents an offset of an operand.
|
||||
*/
|
||||
class OpOffset extends Offset, TOpOffset {
|
||||
Operand op;
|
||||
|
||||
OpOffset() { this = TOpOffset(op) }
|
||||
|
||||
Operand getOperand() { result = op }
|
||||
|
||||
override string toString() { result = "OpOffset(" + op.getDef().toString() + ")" }
|
||||
}
|
||||
|
||||
private int getBaseSizeForPointerType(PointerType type) { result = type.getBaseType().getSize() }
|
||||
|
||||
/**
|
||||
* Holds if pointer `prev` that points at offset `prevOffset + prevOffsetDelta`
|
||||
* steps to `array` that points to `offset + offsetDelta` in one step.
|
||||
* This predicate does not contain any recursive steps.
|
||||
*/
|
||||
bindingset[prevOffset, prevOffsetDelta]
|
||||
predicate simpleArrayLengthStep(
|
||||
Instruction array, Offset offset, int offsetDelta, Instruction prev, Offset prevOffset,
|
||||
int prevOffsetDelta
|
||||
) {
|
||||
// array assign
|
||||
array.(CopyInstruction).getSourceValue() = prev and
|
||||
offset = prevOffset and
|
||||
offsetDelta = prevOffsetDelta
|
||||
or
|
||||
// pointer add with constant
|
||||
array.(PointerAddInstruction).getLeft() = prev and
|
||||
offset = prevOffset and
|
||||
offsetDelta = prevOffsetDelta + getConstantValue(array.(PointerAddInstruction).getRight())
|
||||
or
|
||||
// pointer add with variable
|
||||
array.(PointerAddInstruction).getLeft() = prev and
|
||||
prevOffset instanceof ZeroOffset and
|
||||
offset.(OpOffset).getOperand() = array.(PointerAddInstruction).getRightOperand() and
|
||||
offsetDelta = prevOffsetDelta and
|
||||
not exists(getConstantValue(array.(PointerAddInstruction).getRight()))
|
||||
or
|
||||
// pointer sub with constant
|
||||
array.(PointerSubInstruction).getLeft() = prev and
|
||||
offset = prevOffset and
|
||||
offsetDelta = prevOffsetDelta - getConstantValue(array.(PointerSubInstruction).getRight())
|
||||
or
|
||||
// array to pointer decay
|
||||
array.(ConvertInstruction).getUnary() = prev and
|
||||
array.getConvertedResultExpression() instanceof ArrayToPointerConversion and
|
||||
offset = prevOffset and
|
||||
offsetDelta = prevOffsetDelta
|
||||
or
|
||||
// cast of pointer to pointer with the same element size
|
||||
exists(PointerType fromTyp, PointerType toTyp |
|
||||
array.(PtrToPtrCastInstruction).getUnary() = prev and
|
||||
prev.getResultLanguageType().hasType(fromTyp, false) and
|
||||
array.getResultLanguageType().hasType(toTyp, false) and
|
||||
offset = prevOffset and
|
||||
offsetDelta = prevOffsetDelta and
|
||||
if fromTyp instanceof VoidPointerType
|
||||
then getBaseSizeForPointerType(toTyp) = 1
|
||||
else (
|
||||
if toTyp instanceof VoidPointerType
|
||||
then getBaseSizeForPointerType(fromTyp) = 1
|
||||
else getBaseSizeForPointerType(toTyp) = getBaseSizeForPointerType(fromTyp)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a `sizeExpr` of malloc into a variable part (`lengthExpr`) and an integer offset (`delta`).
|
||||
*/
|
||||
private predicate deconstructMallocSizeExpr(Expr sizeExpr, Expr lengthExpr, int delta) {
|
||||
sizeExpr instanceof AddExpr and
|
||||
exists(Expr constantExpr |
|
||||
lengthExpr = sizeExpr.(AddExpr).getAnOperand() and
|
||||
constantExpr = sizeExpr.(AddExpr).getAnOperand() and
|
||||
lengthExpr != constantExpr and
|
||||
delta = constantExpr.getValue().toInt()
|
||||
)
|
||||
or
|
||||
sizeExpr instanceof SubExpr and
|
||||
exists(Expr constantExpr |
|
||||
lengthExpr = sizeExpr.(SubExpr).getLeftOperand() and
|
||||
constantExpr = sizeExpr.(SubExpr).getRightOperand() and
|
||||
delta = -constantExpr.getValue().toInt()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the instruction `array` is a dynamic memory allocation of `length`+`delta` elements.
|
||||
*/
|
||||
private predicate allocation(Instruction array, Length length, int delta) {
|
||||
exists(AllocationExpr alloc, Type ptrTyp |
|
||||
array.getUnconvertedResultExpression() = alloc and
|
||||
array.getResultLanguageType().hasType(ptrTyp, false) and
|
||||
// ensure that we have the same type of the allocation and the pointer
|
||||
ptrTyp.stripTopLevelSpecifiers().(PointerType).getBaseType().getUnspecifiedType() =
|
||||
alloc.getAllocatedElementType().getUnspecifiedType() and
|
||||
// ensure that the size multiplier of the allocation is the same as the
|
||||
// size of the type we are allocating
|
||||
alloc.getSizeMult() = getBaseSizeForPointerType(ptrTyp) and
|
||||
(
|
||||
length instanceof ZeroLength and
|
||||
delta = alloc.getSizeExpr().getValue().toInt()
|
||||
or
|
||||
not exists(alloc.getSizeExpr().getValue().toInt()) and
|
||||
(
|
||||
exists(Expr lengthExpr |
|
||||
deconstructMallocSizeExpr(alloc.getSizeExpr(), lengthExpr, delta) and
|
||||
length.(VNLength).getInstruction().getConvertedResultExpression() = lengthExpr
|
||||
)
|
||||
or
|
||||
not exists(int d | deconstructMallocSizeExpr(alloc.getSizeExpr(), _, d)) and
|
||||
length.(VNLength).getInstruction().getConvertedResultExpression() = alloc.getSizeExpr() and
|
||||
delta = 0
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `array` is declared as an array with length `length + lengthDelta`
|
||||
*/
|
||||
private predicate arrayDeclaration(Instruction array, Length length, int lengthDelta) {
|
||||
(
|
||||
array instanceof VariableAddressInstruction or
|
||||
array instanceof FieldAddressInstruction
|
||||
) and
|
||||
exists(ArrayType type | array.getResultLanguageType().hasType(type, _) |
|
||||
length instanceof ZeroLength and
|
||||
lengthDelta = type.getArraySize()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `array` is declared as an array or allocated
|
||||
* with length `length + lengthDelta`
|
||||
*/
|
||||
predicate arrayAllocationOrDeclaration(Instruction array, Length length, int lengthDelta) {
|
||||
allocation(array, length, lengthDelta)
|
||||
or
|
||||
// declaration of variable of array type
|
||||
arrayDeclaration(array, length, lengthDelta)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the instruction `array` represents a pointer to a chunk of memory that holds
|
||||
* `length + lengthDelta` elements, using only local analysis.
|
||||
* `array` points at `offset + offsetDelta` in the chunk of memory.
|
||||
* The pointer is in-bounds if `offset + offsetDelta < length + lengthDelta` and
|
||||
* `offset + offsetDelta >= 0` holds.
|
||||
* The pointer is out-of-bounds if `offset + offsetDelta >= length + lengthDelta`
|
||||
* or `offset + offsetDelta < 0` holds.
|
||||
* All pointers in this predicate are guaranteed to be non-null,
|
||||
* but are not guaranteed to be live.
|
||||
*/
|
||||
predicate knownArrayLength(
|
||||
Instruction array, Length length, int lengthDelta, Offset offset, int offsetDelta
|
||||
) {
|
||||
arrayAllocationOrDeclaration(array, length, lengthDelta) and
|
||||
offset instanceof ZeroOffset and
|
||||
offsetDelta = 0
|
||||
or
|
||||
// simple step (no phi nodes)
|
||||
exists(Instruction prev, Offset prevOffset, int prevOffsetDelta |
|
||||
knownArrayLength(prev, length, lengthDelta, prevOffset, prevOffsetDelta) and
|
||||
simpleArrayLengthStep(array, offset, offsetDelta, prev, prevOffset, prevOffsetDelta)
|
||||
)
|
||||
or
|
||||
// merge control flow after phi node - but only if all the bounds agree
|
||||
forex(Instruction input | array.(PhiInstruction).getAnInput() = input |
|
||||
knownArrayLength(input, length, lengthDelta, offset, offsetDelta)
|
||||
)
|
||||
}
|
||||
16
cpp/ql/src/localDefinitions.ql
Normal file
16
cpp/ql/src/localDefinitions.ql
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @name Jump-to-definition links
|
||||
* @description Generates use-definition pairs that provide the data
|
||||
* for jump-to-definition in the code viewer.
|
||||
* @kind definitions
|
||||
* @id cpp/ide-jump-to-definition
|
||||
* @tags ide-contextual-queries/local-definitions
|
||||
*/
|
||||
|
||||
import definitions
|
||||
|
||||
external string selectedSourceFile();
|
||||
|
||||
from Top e, Top def, string kind
|
||||
where def = definitionOf(e, kind) and e.getFile() = getEncodedFile(selectedSourceFile())
|
||||
select e, def, kind
|
||||
16
cpp/ql/src/localReferences.ql
Normal file
16
cpp/ql/src/localReferences.ql
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @name Find-references links
|
||||
* @description Generates use-definition pairs that provide the data
|
||||
* for find-references in the code viewer.
|
||||
* @kind definitions
|
||||
* @id cpp/ide-find-references
|
||||
* @tags ide-contextual-queries/local-references
|
||||
*/
|
||||
|
||||
import definitions
|
||||
|
||||
external string selectedSourceFile();
|
||||
|
||||
from Top e, Top def, string kind
|
||||
where def = definitionOf(e, kind) and def.getFile() = getEncodedFile(selectedSourceFile())
|
||||
select e, def, kind
|
||||
@@ -2,3 +2,4 @@ name: codeql-cpp
|
||||
version: 0.0.0
|
||||
dbscheme: semmlecode.cpp.dbscheme
|
||||
suites: codeql-suites
|
||||
extractor: cpp
|
||||
|
||||
@@ -458,6 +458,15 @@ class Class extends UserType {
|
||||
exists(ClassDerivation d | d.getDerivedClass() = this and d = result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets class derivation number `index` of this class/struct, for example the
|
||||
* `public B` is derivation 1 in the following code:
|
||||
* ```
|
||||
* class D : public A, public B, public C {
|
||||
* ...
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
ClassDerivation getDerivation(int index) {
|
||||
exists(ClassDerivation d | d.getDerivedClass() = this and d.getIndex() = index and d = result)
|
||||
}
|
||||
@@ -900,6 +909,22 @@ class AbstractClass extends Class {
|
||||
class TemplateClass extends Class {
|
||||
TemplateClass() { usertypes(underlyingElement(this), _, 6) }
|
||||
|
||||
/**
|
||||
* Gets a class instantiated from this template.
|
||||
*
|
||||
* For example for `MyTemplateClass<T>` in the following code, the results are
|
||||
* `MyTemplateClass<int>` and `MyTemplateClass<long>`:
|
||||
* ```
|
||||
* template<class T>
|
||||
* class MyTemplateClass {
|
||||
* ...
|
||||
* };
|
||||
*
|
||||
* MyTemplateClass<int> instance;
|
||||
*
|
||||
* MyTemplateClass<long> instance;
|
||||
* ```
|
||||
*/
|
||||
Class getAnInstantiation() {
|
||||
result.isConstructedFrom(this) and
|
||||
exists(result.getATemplateArgument())
|
||||
|
||||
@@ -13,8 +13,20 @@ class Comment extends Locatable, @comment {
|
||||
|
||||
override Location getLocation() { comments(underlyingElement(this), _, result) }
|
||||
|
||||
/**
|
||||
* Gets the text of this comment, including the opening `//` or `/*`, and the closing `*``/` if
|
||||
* present.
|
||||
*/
|
||||
string getContents() { comments(underlyingElement(this), result, _) }
|
||||
|
||||
/**
|
||||
* Gets the AST element this comment is associated with. For example, the comment in the
|
||||
* following code is associated with the declaration of `j`.
|
||||
* ```
|
||||
* int i;
|
||||
* int j; // Comment on j
|
||||
* ```
|
||||
*/
|
||||
Element getCommentedElement() {
|
||||
commentbinding(underlyingElement(this), unresolveElement(result))
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@ private predicate idOf(@compilation x, int y) = equivalenceRelation(id/2)(x, y)
|
||||
* Three things happen to each file during a compilation:
|
||||
*
|
||||
* 1. The file is compiled by a real compiler, such as gcc or VC.
|
||||
* 2. The file is parsed by Semmle's C++ front-end.
|
||||
* 2. The file is parsed by the CodeQL C++ front-end.
|
||||
* 3. The parsed representation is converted to database tables by
|
||||
* Semmle's extractor.
|
||||
* the CodeQL extractor.
|
||||
*
|
||||
* This class provides CPU and elapsed time information for steps 2 and 3,
|
||||
* but not for step 1.
|
||||
@@ -40,6 +40,7 @@ class Compilation extends @compilation {
|
||||
/** Gets a file compiled during this invocation. */
|
||||
File getAFileCompiled() { result = getFileCompiled(_) }
|
||||
|
||||
/** Gets the `i`th file compiled during this invocation */
|
||||
File getFileCompiled(int i) { compilation_compiling_files(this, i, unresolveElement(result)) }
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,7 +25,7 @@ private import semmle.code.cpp.internal.QualifiedName as Q
|
||||
* `DeclarationEntry`, because they always have a unique source location.
|
||||
* `EnumConstant` and `FriendDecl` are both examples of this.
|
||||
*/
|
||||
abstract class Declaration extends Locatable, @declaration {
|
||||
class Declaration extends Locatable, @declaration {
|
||||
/**
|
||||
* Gets the innermost namespace which contains this declaration.
|
||||
*
|
||||
@@ -161,6 +161,7 @@ abstract class Declaration extends Locatable, @declaration {
|
||||
/** Holds if the declaration has a definition. */
|
||||
predicate hasDefinition() { exists(this.getDefinition()) }
|
||||
|
||||
/** DEPRECATED: Use `hasDefinition` instead. */
|
||||
predicate isDefined() { hasDefinition() }
|
||||
|
||||
/** Gets the preferred location of this declaration, if any. */
|
||||
@@ -303,7 +304,7 @@ abstract class DeclarationEntry extends Locatable {
|
||||
* available), or the name declared by this entry otherwise.
|
||||
*/
|
||||
string getCanonicalName() {
|
||||
if getDeclaration().isDefined()
|
||||
if getDeclaration().hasDefinition()
|
||||
then result = getDeclaration().getDefinition().getName()
|
||||
else result = getName()
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ class Diagnostic extends Locatable, @diagnostic {
|
||||
/** Gets the error code for this compiler message. */
|
||||
string getTag() { diagnostics(underlyingElement(this), _, result, _, _, _) }
|
||||
|
||||
/** Holds if `s` is the error code for this compiler message. */
|
||||
predicate hasTag(string s) { this.getTag() = s }
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@ import semmle.code.cpp.Declaration
|
||||
import semmle.code.cpp.metrics.MetricFile
|
||||
|
||||
/** A file or folder. */
|
||||
abstract class Container extends Locatable, @container {
|
||||
class Container extends Locatable, @container {
|
||||
/**
|
||||
* Gets the absolute, canonical path of this container, using forward slashes
|
||||
* as path separator.
|
||||
@@ -28,7 +28,7 @@ abstract class Container extends Locatable, @container {
|
||||
* a bare root prefix, that is, the path has no path segments. A container
|
||||
* whose absolute path has no segments is always a `Folder`, not a `File`.
|
||||
*/
|
||||
abstract string getAbsolutePath();
|
||||
string getAbsolutePath() { none() } // overridden by subclasses
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `getLocation` instead.
|
||||
@@ -36,7 +36,7 @@ abstract class Container extends Locatable, @container {
|
||||
*
|
||||
* For more information see [Providing URLs](https://help.semmle.com/QL/learn-ql/ql/locations.html#providing-urls).
|
||||
*/
|
||||
abstract deprecated string getURL();
|
||||
deprecated string getURL() { none() } // overridden by subclasses
|
||||
|
||||
/**
|
||||
* Gets the relative path of this file or folder from the root folder of the
|
||||
|
||||
@@ -103,6 +103,9 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
|
||||
|
||||
/**
|
||||
* Holds if this function is declared to be `constexpr`.
|
||||
*
|
||||
* Note that this does not hold if the function has been declared
|
||||
* `consteval`.
|
||||
*/
|
||||
predicate isDeclaredConstexpr() { this.hasSpecifier("declared_constexpr") }
|
||||
|
||||
@@ -115,9 +118,16 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
|
||||
* template <typename T> constexpr int g(T x) { return f(x); }
|
||||
* ```
|
||||
* `g<int>` is declared constexpr, but is not constexpr.
|
||||
*
|
||||
* Will also hold if this function is `consteval`.
|
||||
*/
|
||||
predicate isConstexpr() { this.hasSpecifier("is_constexpr") }
|
||||
|
||||
/**
|
||||
* Holds if this function is declared to be `consteval`.
|
||||
*/
|
||||
predicate isConsteval() { this.hasSpecifier("is_consteval") }
|
||||
|
||||
/**
|
||||
* Holds if this function is declared with `__attribute__((naked))` or
|
||||
* `__declspec(naked)`.
|
||||
|
||||
@@ -130,7 +130,7 @@ class NamespaceDeclarationEntry extends Locatable, @namespace_decl {
|
||||
/**
|
||||
* A C++ `using` directive or `using` declaration.
|
||||
*/
|
||||
abstract class UsingEntry extends Locatable, @using {
|
||||
class UsingEntry extends Locatable, @using {
|
||||
override Location getLocation() { usings(underlyingElement(this), _, result) }
|
||||
}
|
||||
|
||||
|
||||
@@ -376,6 +376,8 @@ private predicate isIntegralType(@builtintype type, int kind) {
|
||||
kind = 43
|
||||
or
|
||||
kind = 44
|
||||
or
|
||||
kind = 51
|
||||
)
|
||||
}
|
||||
|
||||
@@ -463,6 +465,8 @@ private predicate integralTypeMapping(int original, int canonical, int unsigned,
|
||||
original = 43 and canonical = 43 and unsigned = -1 and signed = -1 // char16_t
|
||||
or
|
||||
original = 44 and canonical = 44 and unsigned = -1 and signed = -1 // char32_t
|
||||
or
|
||||
original = 51 and canonical = 51 and unsigned = -1 and signed = -1 // char8_t
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -697,28 +701,188 @@ class Int128Type extends IntegralType {
|
||||
override string getCanonicalQLClass() { result = "Int128Type" }
|
||||
}
|
||||
|
||||
private newtype TTypeDomain =
|
||||
TRealDomain() or
|
||||
TComplexDomain() or
|
||||
TImaginaryDomain()
|
||||
|
||||
/**
|
||||
* The C/C++ floating point types. See 4.5. This includes `float`,
|
||||
* `double` and `long double` types.
|
||||
* ```
|
||||
* float f;
|
||||
* double d;
|
||||
* long double ld;
|
||||
* ```
|
||||
* The type domain of a floating-point type. One of `RealDomain`, `ComplexDomain`, or
|
||||
* `ImaginaryDomain`.
|
||||
*/
|
||||
class TypeDomain extends TTypeDomain {
|
||||
/** Gets a textual representation of this type domain. */
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* The type domain of a floating-point type that represents a real number.
|
||||
*/
|
||||
class RealDomain extends TypeDomain, TRealDomain {
|
||||
final override string toString() { result = "real" }
|
||||
}
|
||||
|
||||
/**
|
||||
* The type domain of a floating-point type that represents a complex number.
|
||||
*/
|
||||
class ComplexDomain extends TypeDomain, TComplexDomain {
|
||||
final override string toString() { result = "complex" }
|
||||
}
|
||||
|
||||
/**
|
||||
* The type domain of a floating-point type that represents an imaginary number.
|
||||
*/
|
||||
class ImaginaryDomain extends TypeDomain, TImaginaryDomain {
|
||||
final override string toString() { result = "imaginary" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Data for floating-point types.
|
||||
*
|
||||
* kind: The original type kind. Can be any floating-point type kind.
|
||||
* base: The numeric base of the number's representation. Can be 2 (binary) or 10 (decimal).
|
||||
* domain: The type domain of the type. Can be `RealDomain`, `ComplexDomain`, or `ImaginaryDomain`.
|
||||
* realKind: The type kind of the corresponding real type. For example, the corresponding real type
|
||||
* of `_Complex double` is `double`.
|
||||
* extended: `true` if the number is an extended-precision floating-point number, such as
|
||||
* `_Float32x`.
|
||||
*/
|
||||
private predicate floatingPointTypeMapping(
|
||||
int kind, int base, TTypeDomain domain, int realKind, boolean extended
|
||||
) {
|
||||
// float
|
||||
kind = 24 and base = 2 and domain = TRealDomain() and realKind = 24 and extended = false
|
||||
or
|
||||
// double
|
||||
kind = 25 and base = 2 and domain = TRealDomain() and realKind = 25 and extended = false
|
||||
or
|
||||
// long double
|
||||
kind = 26 and base = 2 and domain = TRealDomain() and realKind = 26 and extended = false
|
||||
or
|
||||
// _Complex float
|
||||
kind = 27 and base = 2 and domain = TComplexDomain() and realKind = 24 and extended = false
|
||||
or
|
||||
// _Complex double
|
||||
kind = 28 and base = 2 and domain = TComplexDomain() and realKind = 25 and extended = false
|
||||
or
|
||||
// _Complex long double
|
||||
kind = 29 and base = 2 and domain = TComplexDomain() and realKind = 26 and extended = false
|
||||
or
|
||||
// _Imaginary float
|
||||
kind = 30 and base = 2 and domain = TImaginaryDomain() and realKind = 24 and extended = false
|
||||
or
|
||||
// _Imaginary double
|
||||
kind = 31 and base = 2 and domain = TImaginaryDomain() and realKind = 25 and extended = false
|
||||
or
|
||||
// _Imaginary long double
|
||||
kind = 32 and base = 2 and domain = TImaginaryDomain() and realKind = 26 and extended = false
|
||||
or
|
||||
// __float128
|
||||
kind = 38 and base = 2 and domain = TRealDomain() and realKind = 38 and extended = false
|
||||
or
|
||||
// _Complex __float128
|
||||
kind = 39 and base = 2 and domain = TComplexDomain() and realKind = 38 and extended = false
|
||||
or
|
||||
// _Decimal32
|
||||
kind = 40 and base = 10 and domain = TRealDomain() and realKind = 40 and extended = false
|
||||
or
|
||||
// _Decimal64
|
||||
kind = 41 and base = 10 and domain = TRealDomain() and realKind = 41 and extended = false
|
||||
or
|
||||
// _Decimal128
|
||||
kind = 42 and base = 10 and domain = TRealDomain() and realKind = 42 and extended = false
|
||||
or
|
||||
// _Float32
|
||||
kind = 45 and base = 2 and domain = TRealDomain() and realKind = 45 and extended = false
|
||||
or
|
||||
// _Float32x
|
||||
kind = 46 and base = 2 and domain = TRealDomain() and realKind = 46 and extended = true
|
||||
or
|
||||
// _Float64
|
||||
kind = 47 and base = 2 and domain = TRealDomain() and realKind = 47 and extended = false
|
||||
or
|
||||
// _Float64x
|
||||
kind = 48 and base = 2 and domain = TRealDomain() and realKind = 48 and extended = true
|
||||
or
|
||||
// _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
|
||||
}
|
||||
|
||||
/**
|
||||
* The C/C++ floating point types. See 4.5. This includes `float`, `double` and `long double`, the
|
||||
* fixed-size floating-point types like `_Float32`, the extended-precision floating-point types like
|
||||
* `_Float64x`, and the decimal floating-point types like `_Decimal32`. It also includes the complex
|
||||
* and imaginary versions of all of these types.
|
||||
*/
|
||||
class FloatingPointType extends ArithmeticType {
|
||||
final int base;
|
||||
final TypeDomain domain;
|
||||
final int realKind;
|
||||
final boolean extended;
|
||||
|
||||
FloatingPointType() {
|
||||
exists(int kind |
|
||||
builtintypes(underlyingElement(this), _, kind, _, _, _) and
|
||||
(
|
||||
kind >= 24 and kind <= 32
|
||||
or
|
||||
kind >= 38 and kind <= 42
|
||||
or
|
||||
kind >= 45 and kind <= 50
|
||||
)
|
||||
floatingPointTypeMapping(kind, base, domain, realKind, extended)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the numeric base of this type's representation: 2 (binary) or 10 (decimal). */
|
||||
final int getBase() { result = base }
|
||||
|
||||
/**
|
||||
* Gets the type domain of this type. Can be `RealDomain`, `ComplexDomain`, or `ImaginaryDomain`.
|
||||
*/
|
||||
final TypeDomain getDomain() { result = domain }
|
||||
|
||||
/**
|
||||
* Gets the corresponding real type of this type. For example, the corresponding real type of
|
||||
* `_Complex double` is `double`.
|
||||
*/
|
||||
final RealNumberType getRealType() {
|
||||
builtintypes(unresolveElement(result), _, realKind, _, _, _)
|
||||
}
|
||||
|
||||
/** Holds if this type is an extended precision floating-point type, such as `_Float32x`. */
|
||||
final predicate isExtendedPrecision() { extended = true }
|
||||
}
|
||||
|
||||
/**
|
||||
* A floating-point type representing a real number.
|
||||
*/
|
||||
class RealNumberType extends FloatingPointType {
|
||||
RealNumberType() { domain instanceof RealDomain }
|
||||
}
|
||||
|
||||
/**
|
||||
* A floating-point type representing a complex number.
|
||||
*/
|
||||
class ComplexNumberType extends FloatingPointType {
|
||||
ComplexNumberType() { domain instanceof ComplexDomain }
|
||||
}
|
||||
|
||||
/**
|
||||
* A floating-point type representing an imaginary number.
|
||||
*/
|
||||
class ImaginaryNumberType extends FloatingPointType {
|
||||
ImaginaryNumberType() { domain instanceof ImaginaryDomain }
|
||||
}
|
||||
|
||||
/**
|
||||
* A floating-point type whose representation is base 2.
|
||||
*/
|
||||
class BinaryFloatingPointType extends FloatingPointType {
|
||||
BinaryFloatingPointType() { base = 2 }
|
||||
}
|
||||
|
||||
/**
|
||||
* A floating-point type whose representation is base 10.
|
||||
*/
|
||||
class DecimalFloatingPointType extends FloatingPointType {
|
||||
DecimalFloatingPointType() { base = 10 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -727,7 +891,7 @@ class FloatingPointType extends ArithmeticType {
|
||||
* float f;
|
||||
* ```
|
||||
*/
|
||||
class FloatType extends FloatingPointType {
|
||||
class FloatType extends RealNumberType, BinaryFloatingPointType {
|
||||
FloatType() { builtintypes(underlyingElement(this), _, 24, _, _, _) }
|
||||
|
||||
override string getCanonicalQLClass() { result = "FloatType" }
|
||||
@@ -739,7 +903,7 @@ class FloatType extends FloatingPointType {
|
||||
* double d;
|
||||
* ```
|
||||
*/
|
||||
class DoubleType extends FloatingPointType {
|
||||
class DoubleType extends RealNumberType, BinaryFloatingPointType {
|
||||
DoubleType() { builtintypes(underlyingElement(this), _, 25, _, _, _) }
|
||||
|
||||
override string getCanonicalQLClass() { result = "DoubleType" }
|
||||
@@ -751,7 +915,7 @@ class DoubleType extends FloatingPointType {
|
||||
* long double ld;
|
||||
* ```
|
||||
*/
|
||||
class LongDoubleType extends FloatingPointType {
|
||||
class LongDoubleType extends RealNumberType, BinaryFloatingPointType {
|
||||
LongDoubleType() { builtintypes(underlyingElement(this), _, 26, _, _, _) }
|
||||
|
||||
override string getCanonicalQLClass() { result = "LongDoubleType" }
|
||||
@@ -763,7 +927,7 @@ class LongDoubleType extends FloatingPointType {
|
||||
* __float128 f128;
|
||||
* ```
|
||||
*/
|
||||
class Float128Type extends FloatingPointType {
|
||||
class Float128Type extends RealNumberType, BinaryFloatingPointType {
|
||||
Float128Type() { builtintypes(underlyingElement(this), _, 38, _, _, _) }
|
||||
|
||||
override string getCanonicalQLClass() { result = "Float128Type" }
|
||||
@@ -775,7 +939,7 @@ class Float128Type extends FloatingPointType {
|
||||
* _Decimal32 d32;
|
||||
* ```
|
||||
*/
|
||||
class Decimal32Type extends FloatingPointType {
|
||||
class Decimal32Type extends RealNumberType, DecimalFloatingPointType {
|
||||
Decimal32Type() { builtintypes(underlyingElement(this), _, 40, _, _, _) }
|
||||
|
||||
override string getCanonicalQLClass() { result = "Decimal32Type" }
|
||||
@@ -787,7 +951,7 @@ class Decimal32Type extends FloatingPointType {
|
||||
* _Decimal64 d64;
|
||||
* ```
|
||||
*/
|
||||
class Decimal64Type extends FloatingPointType {
|
||||
class Decimal64Type extends RealNumberType, DecimalFloatingPointType {
|
||||
Decimal64Type() { builtintypes(underlyingElement(this), _, 41, _, _, _) }
|
||||
|
||||
override string getCanonicalQLClass() { result = "Decimal64Type" }
|
||||
@@ -799,7 +963,7 @@ class Decimal64Type extends FloatingPointType {
|
||||
* _Decimal128 d128;
|
||||
* ```
|
||||
*/
|
||||
class Decimal128Type extends FloatingPointType {
|
||||
class Decimal128Type extends RealNumberType, DecimalFloatingPointType {
|
||||
Decimal128Type() { builtintypes(underlyingElement(this), _, 42, _, _, _) }
|
||||
|
||||
override string getCanonicalQLClass() { result = "Decimal128Type" }
|
||||
@@ -833,6 +997,18 @@ class WideCharType extends IntegralType {
|
||||
override string getCanonicalQLClass() { result = "WideCharType" }
|
||||
}
|
||||
|
||||
/**
|
||||
* The C/C++ `char8_t` type. This is available starting with C++20.
|
||||
* ```
|
||||
* char8_t c8;
|
||||
* ```
|
||||
*/
|
||||
class Char8Type extends IntegralType {
|
||||
Char8Type() { builtintypes(underlyingElement(this), _, 51, _, _, _) }
|
||||
|
||||
override string getCanonicalQLClass() { result = "Char8Type" }
|
||||
}
|
||||
|
||||
/**
|
||||
* The C/C++ `char16_t` type. This is available starting with C11 and C++11.
|
||||
* ```
|
||||
|
||||
@@ -38,7 +38,7 @@ class UserType extends Type, Declaration, NameQualifyingElement, AccessHolder, @
|
||||
override Specifier getASpecifier() { result = Type.super.getASpecifier() }
|
||||
|
||||
override Location getLocation() {
|
||||
if isDefined()
|
||||
if hasDefinition()
|
||||
then result = this.getDefinitionLocation()
|
||||
else result = this.getADeclarationLocation()
|
||||
}
|
||||
|
||||
@@ -126,10 +126,7 @@ class Variable extends Declaration, @variable {
|
||||
or
|
||||
exists(AssignExpr ae | ae.getLValue().(Access).getTarget() = this and result = ae.getRValue())
|
||||
or
|
||||
exists(AggregateLiteral l |
|
||||
this.getDeclaringType() = l.getType() and
|
||||
result = l.getChild(this.(Field).getInitializationOrder())
|
||||
)
|
||||
exists(ClassAggregateLiteral l | result = l.getFieldExpr(this))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,6 +23,8 @@ predicate freeFunction(Function f, int argNum) { argNum = f.(DeallocationFunctio
|
||||
|
||||
/**
|
||||
* A call to a library routine that frees memory.
|
||||
*
|
||||
* DEPRECATED: Use `DeallocationExpr` instead (this also includes `delete` expressions).
|
||||
*/
|
||||
predicate freeCall(FunctionCall fc, Expr arg) { arg = fc.(DeallocationExpr).getFreedExpr() }
|
||||
|
||||
|
||||
@@ -12,12 +12,12 @@ abstract class Assertion extends Locatable {
|
||||
}
|
||||
|
||||
/**
|
||||
* A libc assert, as defined in assert.h. A macro with the head
|
||||
* "assert(expr)" that expands to a conditional expression which
|
||||
* may terminate the program.
|
||||
* A libc assert, as defined in assert.h. A macro with a head
|
||||
* that matches the prefix "assert(", and expands to a conditional
|
||||
* expression which may terminate the program.
|
||||
*/
|
||||
class LibcAssert extends MacroInvocation, Assertion {
|
||||
LibcAssert() { this.getMacro().getHead() = "assert(expr)" }
|
||||
LibcAssert() { this.getMacro().getHead().matches("assert(%") }
|
||||
|
||||
override Expr getAsserted() {
|
||||
exists(ConditionalExpr ce | this.getAGeneratedElement() = ce | result = ce.getCondition())
|
||||
|
||||
@@ -92,13 +92,7 @@ int getBufferSize(Expr bufferExpr, Element why) {
|
||||
// dataflow (all sources must be the same size)
|
||||
bufferExprNode = DataFlow::exprNode(bufferExpr) and
|
||||
result =
|
||||
min(Expr def |
|
||||
DataFlow::localFlowStep(DataFlow::exprNode(def), bufferExprNode)
|
||||
|
|
||||
getBufferSize(def, _)
|
||||
) and
|
||||
result =
|
||||
max(Expr def |
|
||||
unique(Expr def |
|
||||
DataFlow::localFlowStep(DataFlow::exprNode(def), bufferExprNode)
|
||||
|
|
||||
getBufferSize(def, _)
|
||||
|
||||
@@ -532,13 +532,7 @@ library class ExprEvaluator extends int {
|
||||
interestingVariableAccess(e, va, v, true) and
|
||||
// All assignments must have the same int value
|
||||
result =
|
||||
min(Expr value |
|
||||
value = v.getAnAssignedValue() and not ignoreVariableAssignment(e, v, value)
|
||||
|
|
||||
getValueInternalNonSubExpr(value)
|
||||
) and
|
||||
result =
|
||||
max(Expr value |
|
||||
unique(Expr value |
|
||||
value = v.getAnAssignedValue() and not ignoreVariableAssignment(e, v, value)
|
||||
|
|
||||
getValueInternalNonSubExpr(value)
|
||||
|
||||
@@ -166,10 +166,13 @@ private predicate referenceFromVariableAccess(VariableAccess va, Expr reference)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate valueMayEscapeAt(Expr e) {
|
||||
private predicate addressMayEscapeAt(Expr e) {
|
||||
exists(Call call |
|
||||
e = call.getAnArgument().getFullyConverted() and
|
||||
not stdIdentityFunction(call.getTarget())
|
||||
or
|
||||
e = call.getQualifier().getFullyConverted() and
|
||||
e.getUnderlyingType() instanceof PointerType
|
||||
)
|
||||
or
|
||||
exists(AssignExpr assign | e = assign.getRValue().getFullyConverted())
|
||||
@@ -187,8 +190,8 @@ private predicate valueMayEscapeAt(Expr e) {
|
||||
exists(AsmStmt asm | e = asm.getAChild().(Expr).getFullyConverted())
|
||||
}
|
||||
|
||||
private predicate valueMayEscapeMutablyAt(Expr e) {
|
||||
valueMayEscapeAt(e) and
|
||||
private predicate addressMayEscapeMutablyAt(Expr e) {
|
||||
addressMayEscapeAt(e) and
|
||||
exists(Type t | t = e.getType().getUnderlyingType() |
|
||||
exists(PointerType pt |
|
||||
pt = t
|
||||
@@ -207,6 +210,22 @@ private predicate valueMayEscapeMutablyAt(Expr e) {
|
||||
)
|
||||
}
|
||||
|
||||
private predicate lvalueMayEscapeAt(Expr e) {
|
||||
// A call qualifier, like `q` in `q.f()`, is special in that the address of
|
||||
// `q` escapes even though `q` is not a pointer or a reference.
|
||||
exists(Call call |
|
||||
e = call.getQualifier().getFullyConverted() and
|
||||
e.getType().getUnspecifiedType() instanceof Class
|
||||
)
|
||||
}
|
||||
|
||||
private predicate lvalueMayEscapeMutablyAt(Expr e) {
|
||||
lvalueMayEscapeAt(e) and
|
||||
// A qualifier of a call to a const member function is converted to a const
|
||||
// class type.
|
||||
not e.getType().isConst()
|
||||
}
|
||||
|
||||
private predicate addressFromVariableAccess(VariableAccess va, Expr e) {
|
||||
pointerFromVariableAccess(va, e)
|
||||
or
|
||||
@@ -253,8 +272,11 @@ private module EscapesTree_Cached {
|
||||
*/
|
||||
cached
|
||||
predicate variableAddressEscapesTree(VariableAccess va, Expr e) {
|
||||
valueMayEscapeAt(e) and
|
||||
addressMayEscapeAt(e) and
|
||||
addressFromVariableAccess(va, e)
|
||||
or
|
||||
lvalueMayEscapeAt(e) and
|
||||
lvalueFromVariableAccess(va, e)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -283,8 +305,11 @@ private module EscapesTree_Cached {
|
||||
*/
|
||||
cached
|
||||
predicate variableAddressEscapesTreeNonConst(VariableAccess va, Expr e) {
|
||||
valueMayEscapeMutablyAt(e) and
|
||||
addressMayEscapeMutablyAt(e) and
|
||||
addressFromVariableAccess(va, e)
|
||||
or
|
||||
lvalueMayEscapeMutablyAt(e) and
|
||||
lvalueFromVariableAccess(va, e)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* DEPRECATED: Recursion through `DataFlow::Configuration` is impossible in
|
||||
* Semmle Core 1.17 and above. There is no need for this module because it's
|
||||
* any supported tooling. There is no need for this module because it's
|
||||
* impossible to accidentally depend on recursion through
|
||||
* `DataFlow::Configuration` in current releases.
|
||||
*
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -26,13 +26,30 @@ private module Cached {
|
||||
)
|
||||
}
|
||||
|
||||
/** Provides predicates for calculating flow-through summaries. */
|
||||
pragma[nomagic]
|
||||
private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) {
|
||||
viableCallable(call) = result.getCallable() and
|
||||
kind = result.getKind()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a value at return position `pos` can be returned to `out` via `call`,
|
||||
* taking virtual dispatch into account.
|
||||
*/
|
||||
cached
|
||||
predicate viableReturnPosOut(DataFlowCall call, ReturnPosition pos, Node out) {
|
||||
exists(ReturnKindExt kind |
|
||||
pos = viableReturnPos(call, kind) and
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
/** Provides predicates for calculating flow-through summaries. */
|
||||
private module FlowThrough {
|
||||
/**
|
||||
* The first flow-through approximation:
|
||||
*
|
||||
* - Input/output access paths are abstracted with a Boolean parameter
|
||||
* - Input access paths are abstracted with a Boolean parameter
|
||||
* that indicates (non-)emptiness.
|
||||
*/
|
||||
private module Cand {
|
||||
@@ -40,83 +57,47 @@ private module Cached {
|
||||
* Holds if `p` can flow to `node` in the same callable using only
|
||||
* value-preserving steps.
|
||||
*
|
||||
* `read` indicates whether it is contents of `p` that can flow to `node`,
|
||||
* and `stored` indicates whether it flows to contents of `node`.
|
||||
* `read` indicates whether it is contents of `p` that can flow to `node`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate parameterValueFlowCand(
|
||||
ParameterNode p, Node node, boolean read, boolean stored
|
||||
) {
|
||||
private predicate parameterValueFlowCand(ParameterNode p, Node node, boolean read) {
|
||||
p = node and
|
||||
read = false and
|
||||
stored = false
|
||||
read = false
|
||||
or
|
||||
// local flow
|
||||
exists(Node mid |
|
||||
parameterValueFlowCand(p, mid, read, stored) and
|
||||
parameterValueFlowCand(p, mid, read) and
|
||||
simpleLocalFlowStep(mid, node)
|
||||
)
|
||||
or
|
||||
// read
|
||||
exists(Node mid, boolean readMid, boolean storedMid |
|
||||
parameterValueFlowCand(p, mid, readMid, storedMid) and
|
||||
readStep(mid, _, node) and
|
||||
stored = false
|
||||
|
|
||||
// value neither read nor stored prior to read
|
||||
readMid = false and
|
||||
storedMid = false and
|
||||
read = true
|
||||
or
|
||||
// value (possibly read and then) stored prior to read (same content)
|
||||
read = readMid and
|
||||
storedMid = true
|
||||
)
|
||||
or
|
||||
// store
|
||||
exists(Node mid |
|
||||
parameterValueFlowCand(p, mid, read, false) and
|
||||
storeStep(mid, _, node) and
|
||||
stored = true
|
||||
parameterValueFlowCand(p, mid, false) and
|
||||
readStep(mid, _, node) and
|
||||
read = true
|
||||
)
|
||||
or
|
||||
// flow through: no prior read or store
|
||||
// flow through: no prior read
|
||||
exists(ArgumentNode arg |
|
||||
parameterValueFlowArgCand(p, arg, false, false) and
|
||||
argumentValueFlowsThroughCand(arg, node, read, stored)
|
||||
parameterValueFlowArgCand(p, arg, false) and
|
||||
argumentValueFlowsThroughCand(arg, node, read)
|
||||
)
|
||||
or
|
||||
// flow through: no read or store inside method
|
||||
// flow through: no read inside method
|
||||
exists(ArgumentNode arg |
|
||||
parameterValueFlowArgCand(p, arg, read, stored) and
|
||||
argumentValueFlowsThroughCand(arg, node, false, false)
|
||||
)
|
||||
or
|
||||
// flow through: possible prior read and prior store with compatible
|
||||
// flow-through method
|
||||
exists(ArgumentNode arg, boolean mid |
|
||||
parameterValueFlowArgCand(p, arg, read, mid) and
|
||||
argumentValueFlowsThroughCand(arg, node, mid, stored)
|
||||
parameterValueFlowArgCand(p, arg, read) and
|
||||
argumentValueFlowsThroughCand(arg, node, false)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate parameterValueFlowArgCand(
|
||||
ParameterNode p, ArgumentNode arg, boolean read, boolean stored
|
||||
) {
|
||||
parameterValueFlowCand(p, arg, read, stored)
|
||||
private predicate parameterValueFlowArgCand(ParameterNode p, ArgumentNode arg, boolean read) {
|
||||
parameterValueFlowCand(p, arg, read)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate parameterValueFlowsToPreUpdateCand(ParameterNode p, PostUpdateNode n) {
|
||||
parameterValueFlowCand(p, n.getPreUpdateNode(), false, false)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate parameterValueFlowsToPostUpdateCand(
|
||||
ParameterNode p, PostUpdateNode n, boolean read
|
||||
) {
|
||||
parameterValueFlowCand(p, n, read, true)
|
||||
parameterValueFlowCand(p, n.getPreUpdateNode(), false)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,33 +106,21 @@ private module Cached {
|
||||
* into account.
|
||||
*
|
||||
* `read` indicates whether it is contents of `p` that can flow to the return
|
||||
* node, and `stored` indicates whether it flows to contents of the return
|
||||
* node.
|
||||
*/
|
||||
predicate parameterValueFlowReturnCand(
|
||||
ParameterNode p, ReturnKindExt kind, boolean read, boolean stored
|
||||
) {
|
||||
predicate parameterValueFlowReturnCand(ParameterNode p, ReturnKind kind, boolean read) {
|
||||
exists(ReturnNode ret |
|
||||
parameterValueFlowCand(p, ret, read, stored) and
|
||||
kind = TValueReturn(ret.getKind())
|
||||
)
|
||||
or
|
||||
exists(ParameterNode p2, int pos2, PostUpdateNode n |
|
||||
parameterValueFlowsToPostUpdateCand(p, n, read) and
|
||||
parameterValueFlowsToPreUpdateCand(p2, n) and
|
||||
p2.isParameterOf(_, pos2) and
|
||||
kind = TParamUpdate(pos2) and
|
||||
p != p2 and
|
||||
stored = true
|
||||
parameterValueFlowCand(p, ret, read) and
|
||||
kind = ret.getKind()
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate argumentValueFlowsThroughCand0(
|
||||
DataFlowCall call, ArgumentNode arg, ReturnKindExt kind, boolean read, boolean stored
|
||||
DataFlowCall call, ArgumentNode arg, ReturnKind kind, boolean read
|
||||
) {
|
||||
exists(ParameterNode param | viableParamArg(call, param, arg) |
|
||||
parameterValueFlowReturnCand(param, kind, read, stored)
|
||||
parameterValueFlowReturnCand(param, kind, read)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -159,22 +128,19 @@ private module Cached {
|
||||
* Holds if `arg` flows to `out` through a call using only value-preserving steps,
|
||||
* not taking call contexts into account.
|
||||
*
|
||||
* `read` indicates whether it is contents of `arg` that can flow to `out`, and
|
||||
* `stored` indicates whether it flows to contents of `out`.
|
||||
* `read` indicates whether it is contents of `arg` that can flow to `out`.
|
||||
*/
|
||||
predicate argumentValueFlowsThroughCand(
|
||||
ArgumentNode arg, Node out, boolean read, boolean stored
|
||||
) {
|
||||
exists(DataFlowCall call, ReturnKindExt kind |
|
||||
argumentValueFlowsThroughCand0(call, arg, kind, read, stored) and
|
||||
out = kind.getAnOutNode(call)
|
||||
predicate argumentValueFlowsThroughCand(ArgumentNode arg, Node out, boolean read) {
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
argumentValueFlowsThroughCand0(call, arg, kind, read) and
|
||||
out = getAnOutNode(call, kind)
|
||||
)
|
||||
}
|
||||
|
||||
predicate cand(ParameterNode p, Node n) {
|
||||
parameterValueFlowCand(p, n, _, _) and
|
||||
parameterValueFlowCand(p, n, _) and
|
||||
(
|
||||
parameterValueFlowReturnCand(p, _, _, _)
|
||||
parameterValueFlowReturnCand(p, _, _)
|
||||
or
|
||||
parameterValueFlowsToPreUpdateCand(p, _)
|
||||
)
|
||||
@@ -187,7 +153,6 @@ private module Cached {
|
||||
(
|
||||
n instanceof ParameterNode or
|
||||
n instanceof OutNode or
|
||||
n instanceof PostUpdateNode or
|
||||
readStep(_, _, n) or
|
||||
n instanceof CastNode
|
||||
)
|
||||
@@ -200,10 +165,6 @@ private module Cached {
|
||||
or
|
||||
n instanceof ReturnNode
|
||||
or
|
||||
Cand::parameterValueFlowsToPreUpdateCand(_, n)
|
||||
or
|
||||
storeStep(n, _, _)
|
||||
or
|
||||
readStep(n, _, _)
|
||||
or
|
||||
n instanceof CastNode
|
||||
@@ -237,230 +198,140 @@ private module Cached {
|
||||
/**
|
||||
* The final flow-through calculation:
|
||||
*
|
||||
* - Input/output access paths are abstracted with a `ContentOption` parameter
|
||||
* - Input access paths are abstracted with a `ContentOption` parameter
|
||||
* that represents the head of the access path. `TContentNone()` means that
|
||||
* the access path is unrestricted.
|
||||
* - Types are checked using the `compatibleTypes()` relation.
|
||||
*/
|
||||
cached
|
||||
private module Final {
|
||||
/**
|
||||
* Holds if `p` can flow to `node` in the same callable using only
|
||||
* value-preserving steps, not taking call contexts into account.
|
||||
*
|
||||
* `contentIn` describes the content of `p` that can flow to `node`
|
||||
* (if any), and `contentOut` describes the content of `node` that
|
||||
* it flows to (if any).
|
||||
* (if any).
|
||||
*/
|
||||
private predicate parameterValueFlow(
|
||||
ParameterNode p, Node node, ContentOption contentIn, ContentOption contentOut
|
||||
) {
|
||||
parameterValueFlow0(p, node, contentIn, contentOut) and
|
||||
predicate parameterValueFlow(ParameterNode p, Node node, ContentOption contentIn) {
|
||||
parameterValueFlow0(p, node, contentIn) and
|
||||
if node instanceof CastingNode
|
||||
then
|
||||
// normal flow through
|
||||
contentIn = TContentNone() and
|
||||
contentOut = TContentNone() and
|
||||
compatibleTypes(getErasedNodeTypeBound(p), getErasedNodeTypeBound(node))
|
||||
or
|
||||
// getter
|
||||
exists(Content fIn |
|
||||
contentIn.getContent() = fIn and
|
||||
contentOut = TContentNone() and
|
||||
compatibleTypes(fIn.getType(), getErasedNodeTypeBound(node))
|
||||
)
|
||||
or
|
||||
// (getter+)setter
|
||||
exists(Content fOut |
|
||||
contentOut.getContent() = fOut and
|
||||
compatibleTypes(fOut.getContainerType(), getErasedNodeTypeBound(node))
|
||||
)
|
||||
else any()
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate parameterValueFlow0(
|
||||
ParameterNode p, Node node, ContentOption contentIn, ContentOption contentOut
|
||||
) {
|
||||
private predicate parameterValueFlow0(ParameterNode p, Node node, ContentOption contentIn) {
|
||||
p = node and
|
||||
Cand::cand(p, _) and
|
||||
contentIn = TContentNone() and
|
||||
contentOut = TContentNone()
|
||||
contentIn = TContentNone()
|
||||
or
|
||||
// local flow
|
||||
exists(Node mid |
|
||||
parameterValueFlow(p, mid, contentIn, contentOut) and
|
||||
parameterValueFlow(p, mid, contentIn) and
|
||||
LocalFlowBigStep::localFlowBigStep(mid, node)
|
||||
)
|
||||
or
|
||||
// read
|
||||
exists(Node mid, Content f, ContentOption contentInMid, ContentOption contentOutMid |
|
||||
parameterValueFlow(p, mid, contentInMid, contentOutMid) and
|
||||
readStep(mid, f, node)
|
||||
|
|
||||
// value neither read nor stored prior to read
|
||||
contentInMid = TContentNone() and
|
||||
contentOutMid = TContentNone() and
|
||||
contentIn.getContent() = f and
|
||||
contentOut = TContentNone() and
|
||||
Cand::parameterValueFlowReturnCand(p, _, true, _) and
|
||||
compatibleTypes(getErasedNodeTypeBound(p), f.getContainerType())
|
||||
or
|
||||
// value (possibly read and then) stored prior to read (same content)
|
||||
contentIn = contentInMid and
|
||||
contentOutMid.getContent() = f and
|
||||
contentOut = TContentNone()
|
||||
)
|
||||
or
|
||||
// store
|
||||
exists(Node mid, Content f |
|
||||
parameterValueFlow(p, mid, contentIn, TContentNone()) and
|
||||
storeStep(mid, f, node) and
|
||||
contentOut.getContent() = f
|
||||
|
|
||||
contentIn = TContentNone() and
|
||||
compatibleTypes(getErasedNodeTypeBound(p), f.getType())
|
||||
or
|
||||
compatibleTypes(contentIn.getContent().getType(), f.getType())
|
||||
parameterValueFlow(p, mid, TContentNone()) and
|
||||
readStep(mid, f, node) and
|
||||
contentIn.getContent() = f and
|
||||
Cand::parameterValueFlowReturnCand(p, _, true) and
|
||||
compatibleTypes(getErasedNodeTypeBound(p), f.getContainerType())
|
||||
)
|
||||
or
|
||||
// flow through: no prior read or store
|
||||
// flow through: no prior read
|
||||
exists(ArgumentNode arg |
|
||||
parameterValueFlowArg(p, arg, TContentNone(), TContentNone()) and
|
||||
argumentValueFlowsThrough(_, arg, contentIn, contentOut, node)
|
||||
parameterValueFlowArg(p, arg, TContentNone()) and
|
||||
argumentValueFlowsThrough(arg, contentIn, node)
|
||||
)
|
||||
or
|
||||
// flow through: no read or store inside method
|
||||
// flow through: no read inside method
|
||||
exists(ArgumentNode arg |
|
||||
parameterValueFlowArg(p, arg, contentIn, contentOut) and
|
||||
argumentValueFlowsThrough(_, arg, TContentNone(), TContentNone(), node)
|
||||
)
|
||||
or
|
||||
// flow through: possible prior read and prior store with compatible
|
||||
// flow-through method
|
||||
exists(ArgumentNode arg, ContentOption contentMid |
|
||||
parameterValueFlowArg(p, arg, contentIn, contentMid) and
|
||||
argumentValueFlowsThrough(_, arg, contentMid, contentOut, node)
|
||||
parameterValueFlowArg(p, arg, contentIn) and
|
||||
argumentValueFlowsThrough(arg, TContentNone(), node)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate parameterValueFlowArg(
|
||||
ParameterNode p, ArgumentNode arg, ContentOption contentIn, ContentOption contentOut
|
||||
ParameterNode p, ArgumentNode arg, ContentOption contentIn
|
||||
) {
|
||||
parameterValueFlow(p, arg, contentIn, contentOut) and
|
||||
Cand::argumentValueFlowsThroughCand(arg, _, _, _)
|
||||
parameterValueFlow(p, arg, contentIn) and
|
||||
Cand::argumentValueFlowsThroughCand(arg, _, _)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate argumentValueFlowsThrough0(
|
||||
DataFlowCall call, ArgumentNode arg, ReturnKindExt kind, ContentOption contentIn,
|
||||
ContentOption contentOut
|
||||
DataFlowCall call, ArgumentNode arg, ReturnKind kind, ContentOption contentIn
|
||||
) {
|
||||
exists(ParameterNode param | viableParamArg(call, param, arg) |
|
||||
parameterValueFlowReturn(param, _, kind, contentIn, contentOut)
|
||||
parameterValueFlowReturn(param, kind, contentIn)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `arg` flows to `out` through `call` using only value-preserving steps,
|
||||
* Holds if `arg` flows to `out` through a call using only value-preserving steps,
|
||||
* not taking call contexts into account.
|
||||
*
|
||||
* `contentIn` describes the content of `arg` that can flow to `out` (if any), and
|
||||
* `contentOut` describes the content of `out` that it flows to (if any).
|
||||
* `contentIn` describes the content of `arg` that can flow to `out` (if any).
|
||||
*/
|
||||
cached
|
||||
predicate argumentValueFlowsThrough(
|
||||
DataFlowCall call, ArgumentNode arg, ContentOption contentIn, ContentOption contentOut,
|
||||
Node out
|
||||
) {
|
||||
exists(ReturnKindExt kind |
|
||||
argumentValueFlowsThrough0(call, arg, kind, contentIn, contentOut) and
|
||||
out = kind.getAnOutNode(call)
|
||||
pragma[nomagic]
|
||||
predicate argumentValueFlowsThrough(ArgumentNode arg, ContentOption contentIn, Node out) {
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
argumentValueFlowsThrough0(call, arg, kind, contentIn) and
|
||||
out = getAnOutNode(call, kind)
|
||||
|
|
||||
// normal flow through
|
||||
contentIn = TContentNone() and
|
||||
contentOut = TContentNone() and
|
||||
compatibleTypes(getErasedNodeTypeBound(arg), getErasedNodeTypeBound(out))
|
||||
or
|
||||
// getter
|
||||
exists(Content fIn |
|
||||
contentIn.getContent() = fIn and
|
||||
contentOut = TContentNone() and
|
||||
compatibleTypes(getErasedNodeTypeBound(arg), fIn.getContainerType()) and
|
||||
compatibleTypes(fIn.getType(), getErasedNodeTypeBound(out))
|
||||
)
|
||||
or
|
||||
// setter
|
||||
exists(Content fOut |
|
||||
contentIn = TContentNone() and
|
||||
contentOut.getContent() = fOut and
|
||||
compatibleTypes(getErasedNodeTypeBound(arg), fOut.getType()) and
|
||||
compatibleTypes(fOut.getContainerType(), getErasedNodeTypeBound(out))
|
||||
)
|
||||
or
|
||||
// getter+setter
|
||||
exists(Content fIn, Content fOut |
|
||||
contentIn.getContent() = fIn and
|
||||
contentOut.getContent() = fOut and
|
||||
compatibleTypes(getErasedNodeTypeBound(arg), fIn.getContainerType()) and
|
||||
compatibleTypes(fOut.getContainerType(), getErasedNodeTypeBound(out))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `p` can flow to the pre-update node associated with post-update
|
||||
* node `n`, in the same callable, using only value-preserving steps.
|
||||
*/
|
||||
cached
|
||||
predicate parameterValueFlowsToPreUpdate(ParameterNode p, PostUpdateNode n) {
|
||||
parameterValueFlow(p, n.getPreUpdateNode(), TContentNone(), TContentNone())
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate parameterValueFlowsToPostUpdate(
|
||||
ParameterNode p, PostUpdateNode n, ContentOption contentIn, ContentOption contentOut
|
||||
) {
|
||||
parameterValueFlow(p, n, contentIn, contentOut) and
|
||||
contentOut.hasContent()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `p` can flow to a return node of kind `kind` in the same
|
||||
* callable using only value-preserving steps.
|
||||
*
|
||||
* `contentIn` describes the content of `p` that can flow to the return
|
||||
* node (if any), and `contentOut` describes the content of the return
|
||||
* node that it flows to (if any).
|
||||
* node (if any).
|
||||
*/
|
||||
cached
|
||||
predicate parameterValueFlowReturn(
|
||||
ParameterNode p, Node ret, ReturnKindExt kind, ContentOption contentIn,
|
||||
ContentOption contentOut
|
||||
private predicate parameterValueFlowReturn(
|
||||
ParameterNode p, ReturnKind kind, ContentOption contentIn
|
||||
) {
|
||||
ret =
|
||||
any(ReturnNode n |
|
||||
parameterValueFlow(p, n, contentIn, contentOut) and
|
||||
kind = TValueReturn(n.getKind())
|
||||
)
|
||||
or
|
||||
ret =
|
||||
any(PostUpdateNode n |
|
||||
exists(ParameterNode p2, int pos2 |
|
||||
parameterValueFlowsToPostUpdate(p, n, contentIn, contentOut) and
|
||||
parameterValueFlowsToPreUpdate(p2, n) and
|
||||
p2.isParameterOf(_, pos2) and
|
||||
kind = TParamUpdate(pos2) and
|
||||
p != p2
|
||||
)
|
||||
)
|
||||
exists(ReturnNode ret |
|
||||
parameterValueFlow(p, ret, contentIn) and
|
||||
kind = ret.getKind()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
import Final
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `p` can flow to the pre-update node associated with post-update
|
||||
* node `n`, in the same callable, using only value-preserving steps.
|
||||
*/
|
||||
cached
|
||||
predicate parameterValueFlowsToPreUpdate(ParameterNode p, PostUpdateNode n) {
|
||||
parameterValueFlow(p, n.getPreUpdateNode(), TContentNone())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data can flow from `node1` to `node2` via a direct assignment to
|
||||
* `f`.
|
||||
@@ -469,14 +340,14 @@ private module Cached {
|
||||
* been stored into, in order to handle cases like `x.f1.f2 = y`.
|
||||
*/
|
||||
cached
|
||||
predicate storeDirect(Node node1, Content f, Node node2) {
|
||||
predicate store(Node node1, Content f, Node node2) {
|
||||
storeStep(node1, f, node2) and readStep(_, f, _)
|
||||
or
|
||||
exists(Node n1, Node n2 |
|
||||
n1 = node1.(PostUpdateNode).getPreUpdateNode() and
|
||||
n2 = node2.(PostUpdateNode).getPreUpdateNode()
|
||||
|
|
||||
argumentValueFlowsThrough(_, n2, TContentSome(f), TContentNone(), n1)
|
||||
argumentValueFlowsThrough(n2, TContentSome(f), n1)
|
||||
or
|
||||
readStep(n2, f, n1)
|
||||
)
|
||||
@@ -520,6 +391,21 @@ private module Cached {
|
||||
newtype TReturnKindExt =
|
||||
TValueReturn(ReturnKind kind) or
|
||||
TParamUpdate(int pos) { exists(ParameterNode p | p.isParameterOf(_, pos)) }
|
||||
|
||||
cached
|
||||
newtype TBooleanOption =
|
||||
TBooleanNone() or
|
||||
TBooleanSome(boolean b) { b = true or b = false }
|
||||
|
||||
cached
|
||||
newtype TAccessPathFront =
|
||||
TFrontNil(DataFlowType t) or
|
||||
TFrontHead(Content f)
|
||||
|
||||
cached
|
||||
newtype TAccessPathFrontOption =
|
||||
TAccessPathFrontNone() or
|
||||
TAccessPathFrontSome(AccessPathFront apf)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -529,8 +415,7 @@ class CastingNode extends Node {
|
||||
CastingNode() {
|
||||
this instanceof ParameterNode or
|
||||
this instanceof CastNode or
|
||||
this instanceof OutNode or
|
||||
this.(PostUpdateNode).getPreUpdateNode() instanceof ArgumentNode
|
||||
this instanceof OutNodeExt
|
||||
}
|
||||
}
|
||||
|
||||
@@ -538,7 +423,7 @@ newtype TContentOption =
|
||||
TContentNone() or
|
||||
TContentSome(Content f)
|
||||
|
||||
class ContentOption extends TContentOption {
|
||||
private class ContentOption extends TContentOption {
|
||||
Content getContent() { this = TContentSome(result) }
|
||||
|
||||
predicate hasContent() { exists(this.getContent()) }
|
||||
@@ -678,6 +563,18 @@ class ReturnNodeExt extends Node {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A node to which data can flow from a call. Either an ordinary out node
|
||||
* or a post-update node associated with a call argument.
|
||||
*/
|
||||
class OutNodeExt extends Node {
|
||||
OutNodeExt() {
|
||||
this instanceof OutNode
|
||||
or
|
||||
this.(PostUpdateNode).getPreUpdateNode() instanceof ArgumentNode
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An extended return kind. A return kind describes how data can be returned
|
||||
* from a callable. This can either be through a returned value or an updated
|
||||
@@ -688,7 +585,7 @@ abstract class ReturnKindExt extends TReturnKindExt {
|
||||
abstract string toString();
|
||||
|
||||
/** Gets a node corresponding to data flow out of `call`. */
|
||||
abstract Node getAnOutNode(DataFlowCall call);
|
||||
abstract OutNodeExt getAnOutNode(DataFlowCall call);
|
||||
}
|
||||
|
||||
class ValueReturnKind extends ReturnKindExt, TValueReturn {
|
||||
@@ -700,7 +597,9 @@ class ValueReturnKind extends ReturnKindExt, TValueReturn {
|
||||
|
||||
override string toString() { result = kind.toString() }
|
||||
|
||||
override Node getAnOutNode(DataFlowCall call) { result = getAnOutNode(call, this.getKind()) }
|
||||
override OutNodeExt getAnOutNode(DataFlowCall call) {
|
||||
result = getAnOutNode(call, this.getKind())
|
||||
}
|
||||
}
|
||||
|
||||
class ParamUpdateReturnKind extends ReturnKindExt, TParamUpdate {
|
||||
@@ -712,9 +611,9 @@ class ParamUpdateReturnKind extends ReturnKindExt, TParamUpdate {
|
||||
|
||||
override string toString() { result = "param update " + pos }
|
||||
|
||||
override PostUpdateNode getAnOutNode(DataFlowCall call) {
|
||||
override OutNodeExt getAnOutNode(DataFlowCall call) {
|
||||
exists(ArgumentNode arg |
|
||||
result.getPreUpdateNode() = arg and
|
||||
result.(PostUpdateNode).getPreUpdateNode() = arg and
|
||||
arg.argumentOf(call, this.getPosition())
|
||||
)
|
||||
}
|
||||
@@ -779,77 +678,58 @@ DataFlowCallable resolveCall(DataFlowCall call, CallContext cc) {
|
||||
result = viableCallable(call) and cc instanceof CallContextReturn
|
||||
}
|
||||
|
||||
newtype TSummary =
|
||||
TSummaryVal() or
|
||||
TSummaryTaint() or
|
||||
TSummaryReadVal(Content f) or
|
||||
TSummaryReadTaint(Content f) or
|
||||
TSummaryTaintStore(Content f)
|
||||
|
||||
/**
|
||||
* A summary of flow through a callable. This can either be value-preserving
|
||||
* if no additional steps are used, taint-flow if at least one additional step
|
||||
* is used, or any one of those combined with a store or a read. Summaries
|
||||
* recorded at a return node are restricted to include at least one additional
|
||||
* step, as the value-based summaries are calculated independent of the
|
||||
* configuration.
|
||||
*/
|
||||
class Summary extends TSummary {
|
||||
string toString() {
|
||||
result = "Val" and this = TSummaryVal()
|
||||
or
|
||||
result = "Taint" and this = TSummaryTaint()
|
||||
or
|
||||
exists(Content f |
|
||||
result = "ReadVal " + f.toString() and this = TSummaryReadVal(f)
|
||||
or
|
||||
result = "ReadTaint " + f.toString() and this = TSummaryReadTaint(f)
|
||||
or
|
||||
result = "TaintStore " + f.toString() and this = TSummaryTaintStore(f)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the summary that results from extending this with an additional step. */
|
||||
Summary additionalStep() {
|
||||
this = TSummaryVal() and result = TSummaryTaint()
|
||||
or
|
||||
this = TSummaryTaint() and result = TSummaryTaint()
|
||||
or
|
||||
exists(Content f | this = TSummaryReadVal(f) and result = TSummaryReadTaint(f))
|
||||
or
|
||||
exists(Content f | this = TSummaryReadTaint(f) and result = TSummaryReadTaint(f))
|
||||
}
|
||||
|
||||
/** Gets the summary that results from extending this with a read. */
|
||||
Summary readStep(Content f) { this = TSummaryVal() and result = TSummaryReadVal(f) }
|
||||
|
||||
/** Gets the summary that results from extending this with a store. */
|
||||
Summary storeStep(Content f) { this = TSummaryTaint() and result = TSummaryTaintStore(f) }
|
||||
|
||||
/** Gets the summary that results from extending this with `step`. */
|
||||
bindingset[this, step]
|
||||
Summary compose(Summary step) {
|
||||
this = TSummaryVal() and result = step
|
||||
or
|
||||
this = TSummaryTaint() and
|
||||
(step = TSummaryTaint() or step = TSummaryTaintStore(_)) and
|
||||
result = step
|
||||
or
|
||||
exists(Content f |
|
||||
this = TSummaryReadVal(f) and step = TSummaryTaint() and result = TSummaryReadTaint(f)
|
||||
)
|
||||
or
|
||||
this = TSummaryReadTaint(_) and step = TSummaryTaint() and result = this
|
||||
}
|
||||
|
||||
/** Holds if this summary does not include any taint steps. */
|
||||
predicate isPartial() {
|
||||
this = TSummaryVal() or
|
||||
this = TSummaryReadVal(_)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
DataFlowType getErasedNodeTypeBound(Node n) { result = getErasedRepr(n.getTypeBound()) }
|
||||
|
||||
predicate readDirect = readStep/3;
|
||||
predicate read = readStep/3;
|
||||
|
||||
/** An optional Boolean value. */
|
||||
class BooleanOption extends TBooleanOption {
|
||||
string toString() {
|
||||
this = TBooleanNone() and result = "<none>"
|
||||
or
|
||||
this = TBooleanSome(any(boolean b | result = b.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The front of an access path. This is either a head or a nil.
|
||||
*/
|
||||
abstract class AccessPathFront extends TAccessPathFront {
|
||||
abstract string toString();
|
||||
|
||||
abstract DataFlowType getType();
|
||||
|
||||
abstract boolean toBoolNonEmpty();
|
||||
|
||||
predicate headUsesContent(Content f) { this = TFrontHead(f) }
|
||||
}
|
||||
|
||||
class AccessPathFrontNil extends AccessPathFront, TFrontNil {
|
||||
override string toString() {
|
||||
exists(DataFlowType t | this = TFrontNil(t) | result = ppReprType(t))
|
||||
}
|
||||
|
||||
override DataFlowType getType() { this = TFrontNil(result) }
|
||||
|
||||
override boolean toBoolNonEmpty() { result = false }
|
||||
}
|
||||
|
||||
class AccessPathFrontHead extends AccessPathFront, TFrontHead {
|
||||
override string toString() { exists(Content f | this = TFrontHead(f) | result = f.toString()) }
|
||||
|
||||
override DataFlowType getType() {
|
||||
exists(Content head | this = TFrontHead(head) | result = head.getContainerType())
|
||||
}
|
||||
|
||||
override boolean toBoolNonEmpty() { result = true }
|
||||
}
|
||||
|
||||
/** An optional access path front. */
|
||||
class AccessPathFrontOption extends TAccessPathFrontOption {
|
||||
string toString() {
|
||||
this = TAccessPathFrontNone() and result = "<none>"
|
||||
or
|
||||
this = TAccessPathFrontSome(any(AccessPathFront apf | result = apf.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -43,7 +43,7 @@ class Node extends TNode {
|
||||
/**
|
||||
* INTERNAL: Do not use. Alternative name for `getFunction`.
|
||||
*/
|
||||
Function getEnclosingCallable() { result = this.getFunction() }
|
||||
final Function getEnclosingCallable() { result = unique(Function f | f = this.getFunction() | f) }
|
||||
|
||||
/** Gets the type of this node. */
|
||||
Type getType() { none() } // overridden in subclasses
|
||||
@@ -299,7 +299,7 @@ private class PartialDefinitionNode extends PostUpdateNode, TPartialDefinitionNo
|
||||
|
||||
override Node getPreUpdateNode() { result.asExpr() = pd.getDefinedExpr() }
|
||||
|
||||
override Location getLocation() { result = pd.getLocation() }
|
||||
override Location getLocation() { result = pd.getActualLocation() }
|
||||
|
||||
PartialDefinition getPartialDefinition() { result = pd }
|
||||
|
||||
@@ -496,8 +496,6 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
// Expr -> Expr
|
||||
exprToExprStep_nocfg(nodeFrom.asExpr(), nodeTo.asExpr())
|
||||
or
|
||||
exprToExprStep_nocfg(nodeFrom.(PostUpdateNode).getPreUpdateNode().asExpr(), nodeTo.asExpr())
|
||||
or
|
||||
// Node -> FlowVar -> VariableAccess
|
||||
exists(FlowVar var |
|
||||
(
|
||||
@@ -657,7 +655,7 @@ private module FieldFlow {
|
||||
exists(FieldConfiguration cfg | cfg.hasFlow(node1, node2)) and
|
||||
// This configuration should not be able to cross function boundaries, but
|
||||
// we double-check here just to be sure.
|
||||
node1.getFunction() = node2.getFunction()
|
||||
node1.getEnclosingCallable() = node2.getEnclosingCallable()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -113,44 +113,39 @@ class FlowVar extends TFlowVar {
|
||||
* ```
|
||||
*/
|
||||
private module PartialDefinitions {
|
||||
private newtype TPartialDefinition =
|
||||
TExplicitFieldStoreQualifier(Expr qualifier, ControlFlowNode node) {
|
||||
exists(FieldAccess fa | qualifier = fa.getQualifier() |
|
||||
private predicate isInstanceFieldWrite(FieldAccess fa, ControlFlowNode node) {
|
||||
assignmentLikeOperation(node, _, fa, _)
|
||||
}
|
||||
|
||||
class PartialDefinition extends Expr {
|
||||
ControlFlowNode node;
|
||||
|
||||
PartialDefinition() {
|
||||
exists(FieldAccess fa | this = fa.getQualifier() |
|
||||
// `fa = ...`, `fa += ...`, etc.
|
||||
isInstanceFieldWrite(fa, node)
|
||||
or
|
||||
// `fa.a = ...`, `f(&fa)`, etc.
|
||||
exists(PartialDefinition pd |
|
||||
node = pd.getSubBasicBlockStart() and
|
||||
fa = pd.getDefinedExpr()
|
||||
)
|
||||
)
|
||||
} or
|
||||
TExplicitCallQualifier(Expr qualifier) {
|
||||
or
|
||||
// `e.f(...)`
|
||||
exists(Call call |
|
||||
qualifier = call.getQualifier() and
|
||||
this = call.getQualifier() and
|
||||
not call.getTarget().hasSpecifier("const")
|
||||
)
|
||||
} or
|
||||
TReferenceArgument(Expr arg, VariableAccess va) { referenceArgument(va, arg) }
|
||||
|
||||
private predicate isInstanceFieldWrite(FieldAccess fa, ControlFlowNode node) {
|
||||
assignmentLikeOperation(node, _, fa, _)
|
||||
}
|
||||
|
||||
class PartialDefinition extends TPartialDefinition {
|
||||
Expr definedExpr;
|
||||
ControlFlowNode node;
|
||||
|
||||
PartialDefinition() {
|
||||
this = TExplicitFieldStoreQualifier(definedExpr, node)
|
||||
) and
|
||||
node = this
|
||||
or
|
||||
this = TExplicitCallQualifier(definedExpr) and node = definedExpr
|
||||
or
|
||||
this = TReferenceArgument(definedExpr, node)
|
||||
// `f(e)`, `f(&e)`, etc.
|
||||
referenceArgument(node, this)
|
||||
}
|
||||
|
||||
predicate partiallyDefines(Variable v) { definedExpr = v.getAnAccess() }
|
||||
predicate partiallyDefines(Variable v) { this = v.getAnAccess() }
|
||||
|
||||
predicate partiallyDefinesThis(ThisExpr e) { definedExpr = e }
|
||||
predicate partiallyDefinesThis(ThisExpr e) { this = e }
|
||||
|
||||
/**
|
||||
* Gets the subBasicBlock where this `PartialDefinition` is defined.
|
||||
@@ -165,33 +160,29 @@ private module PartialDefinitions {
|
||||
* ```
|
||||
* The expression `x` is being partially defined.
|
||||
*/
|
||||
Expr getDefinedExpr() { result = definedExpr }
|
||||
Expr getDefinedExpr() { result = this }
|
||||
|
||||
Location getLocation() {
|
||||
not exists(definedExpr.getLocation()) and result = definedExpr.getParent().getLocation()
|
||||
/**
|
||||
* Gets the location of this element, adjusted to avoid unknown locations
|
||||
* on compiler-generated `ThisExpr`s.
|
||||
*/
|
||||
Location getActualLocation() {
|
||||
not exists(this.getLocation()) and result = this.getParent().getLocation()
|
||||
or
|
||||
definedExpr.getLocation() instanceof UnknownLocation and
|
||||
result = definedExpr.getParent().getLocation()
|
||||
this.getLocation() instanceof UnknownLocation and
|
||||
result = this.getParent().getLocation()
|
||||
or
|
||||
result = definedExpr.getLocation() and not result instanceof UnknownLocation
|
||||
result = this.getLocation() and not result instanceof UnknownLocation
|
||||
}
|
||||
|
||||
string toString() { result = "partial def of " + definedExpr }
|
||||
}
|
||||
|
||||
/**
|
||||
* A partial definition that's a definition by reference.
|
||||
*/
|
||||
class DefinitionByReference extends PartialDefinition, TReferenceArgument {
|
||||
class DefinitionByReference extends PartialDefinition {
|
||||
VariableAccess va;
|
||||
|
||||
DefinitionByReference() {
|
||||
// `this` is not restricted in this charpred. That's because the full
|
||||
// extent of this class includes the charpred of the superclass, which
|
||||
// relates `this` to `definedExpr`, and `va` is functionally determined
|
||||
// by `definedExpr`.
|
||||
referenceArgument(va, definedExpr)
|
||||
}
|
||||
DefinitionByReference() { referenceArgument(va, this) }
|
||||
|
||||
VariableAccess getVariableAccess() { result = va }
|
||||
|
||||
|
||||
@@ -338,7 +338,7 @@ class PointerToFieldLiteral extends ImplicitThisFieldAccess {
|
||||
* int myFunctionTarget(int);
|
||||
*
|
||||
* void myFunction() {
|
||||
* int (*myFunctionPointer)(int) = &myTarget;
|
||||
* int (*myFunctionPointer)(int) = &myFunctionTarget;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
|
||||
@@ -9,9 +9,8 @@ private import semmle.code.cpp.dataflow.EscapesTree
|
||||
*/
|
||||
abstract class Call extends Expr, NameQualifiableElement {
|
||||
/**
|
||||
* Gets the number of actual parameters in this call; use
|
||||
* `getArgument(i)` with `i` between `0` and `result - 1` to
|
||||
* retrieve actuals.
|
||||
* Gets the number of arguments (actual parameters) of this call. The count
|
||||
* does _not_ include the qualifier of the call, if any.
|
||||
*/
|
||||
int getNumberOfArguments() { result = count(this.getAnArgument()) }
|
||||
|
||||
@@ -32,21 +31,24 @@ abstract class Call extends Expr, NameQualifiableElement {
|
||||
Expr getQualifier() { result = this.getChild(-1) }
|
||||
|
||||
/**
|
||||
* Gets an argument for this call.
|
||||
* Gets an argument for this call. To get the qualifier of this call, if
|
||||
* any, use `getQualifier()`.
|
||||
*/
|
||||
Expr getAnArgument() { exists(int i | result = this.getChild(i) and i >= 0) }
|
||||
|
||||
/**
|
||||
* Gets the nth argument for this call.
|
||||
*
|
||||
* The range of `n` is from `0` to `getNumberOfArguments() - 1`.
|
||||
* The range of `n` is from `0` to `getNumberOfArguments() - 1`. To get the
|
||||
* qualifier of this call, if any, use `getQualifier()`.
|
||||
*/
|
||||
Expr getArgument(int n) { result = this.getChild(n) and n >= 0 }
|
||||
|
||||
/**
|
||||
* Gets a sub expression of the argument at position `index`. If the
|
||||
* Gets a subexpression of the argument at position `index`. If the
|
||||
* argument itself contains calls, such calls will be considered
|
||||
* leafs in the expression tree.
|
||||
* leaves in the expression tree. The qualifier of the call, if any, is not
|
||||
* considered to be an argument.
|
||||
*
|
||||
* Example: the call `f(2, 3 + 4, g(4 + 5))` has sub expression(s)
|
||||
* `2` at index 0; `3`, `4`, and `3 + 4` at index 1; and `g(4 + 5)`
|
||||
|
||||
@@ -34,10 +34,10 @@ abstract class Conversion extends Expr {
|
||||
* a `PointerBaseClassConversion`, or some other semantic conversion. Similarly,
|
||||
* a `PointerDerivedClassConversion` may also be a `CStyleCast` or a `StaticCast`.
|
||||
*
|
||||
* This is an abstract root QL class representing the different casts. For
|
||||
* This is a root QL class representing the different casts. For
|
||||
* specific examples, consult the documentation for any of QL classes mentioned above.
|
||||
*/
|
||||
abstract class Cast extends Conversion, @cast {
|
||||
class Cast extends Conversion, @cast {
|
||||
/**
|
||||
* Gets a string describing the semantic conversion operation being performed by
|
||||
* this cast.
|
||||
@@ -699,7 +699,7 @@ class SizeofPackOperator extends Expr, @sizeof_pack {
|
||||
/**
|
||||
* A C/C++ sizeof expression.
|
||||
*/
|
||||
abstract class SizeofOperator extends Expr, @runtime_sizeof {
|
||||
class SizeofOperator extends Expr, @runtime_sizeof {
|
||||
override int getPrecedence() { result = 16 }
|
||||
}
|
||||
|
||||
@@ -762,7 +762,7 @@ class SizeofTypeOperator extends SizeofOperator {
|
||||
/**
|
||||
* A C++11 `alignof` expression.
|
||||
*/
|
||||
abstract class AlignofOperator extends Expr, @runtime_alignof {
|
||||
class AlignofOperator extends Expr, @runtime_alignof {
|
||||
override int getPrecedence() { result = 16 }
|
||||
}
|
||||
|
||||
|
||||
@@ -145,8 +145,6 @@ class HexLiteral extends Literal {
|
||||
|
||||
/**
|
||||
* A C/C++ aggregate literal.
|
||||
*
|
||||
* For example:
|
||||
*/
|
||||
class AggregateLiteral extends Expr, @aggregateliteral {
|
||||
override string getCanonicalQLClass() { result = "AggregateLiteral" }
|
||||
|
||||
@@ -2,6 +2,7 @@ import cpp
|
||||
import semmle.code.cpp.security.Security
|
||||
private import semmle.code.cpp.ir.dataflow.DataFlow
|
||||
private import semmle.code.cpp.ir.dataflow.DataFlow2
|
||||
private import semmle.code.cpp.ir.dataflow.DataFlow3
|
||||
private import semmle.code.cpp.ir.IR
|
||||
private import semmle.code.cpp.ir.dataflow.internal.DataFlowDispatch as Dispatch
|
||||
private import semmle.code.cpp.models.interfaces.Taint
|
||||
@@ -143,7 +144,17 @@ private predicate writesVariable(StoreInstruction store, Variable var) {
|
||||
}
|
||||
|
||||
/**
|
||||
* A variable that has any kind of upper-bound check anywhere in the program
|
||||
* A variable that has any kind of upper-bound check anywhere in the program. This is
|
||||
* biased towards being inclusive because there are a lot of valid ways of doing an
|
||||
* upper bounds checks if we don't consider where it occurs, for example:
|
||||
* ```
|
||||
* if (x < 10) { sink(x); }
|
||||
*
|
||||
* if (10 > y) { sink(y); }
|
||||
*
|
||||
* if (z > 10) { z = 10; }
|
||||
* sink(z);
|
||||
* ```
|
||||
*/
|
||||
// TODO: This coarse overapproximation, ported from the old taint tracking
|
||||
// library, could be replaced with an actual semantic check that a particular
|
||||
@@ -152,10 +163,10 @@ private predicate writesVariable(StoreInstruction store, Variable var) {
|
||||
// previously suppressed by this predicate by coincidence.
|
||||
private predicate hasUpperBoundsCheck(Variable var) {
|
||||
exists(RelationalOperation oper, VariableAccess access |
|
||||
oper.getLeftOperand() = access and
|
||||
oper.getAnOperand() = access and
|
||||
access.getTarget() = var and
|
||||
// Comparing to 0 is not an upper bound check
|
||||
not oper.getRightOperand().getValue() = "0"
|
||||
not oper.getAnOperand().getValue() = "0"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -171,6 +182,7 @@ private predicate nodeIsBarrierIn(DataFlow::Node node) {
|
||||
node = getNodeForSource(any(Expr e))
|
||||
}
|
||||
|
||||
cached
|
||||
private predicate instructionTaintStep(Instruction i1, Instruction i2) {
|
||||
// Expressions computed from tainted data are also tainted
|
||||
exists(CallInstruction call, int argIndex | call = i2 |
|
||||
@@ -187,6 +199,14 @@ private predicate instructionTaintStep(Instruction i1, Instruction i2) {
|
||||
// Flow through pointer dereference
|
||||
i2.(LoadInstruction).getSourceAddress() = i1
|
||||
or
|
||||
// Flow through partial reads of arrays and unions
|
||||
i2.(LoadInstruction).getSourceValueOperand().getAnyDef() = i1 and
|
||||
not i1.isResultConflated() and
|
||||
(
|
||||
i1.getResultType() instanceof ArrayType or
|
||||
i1.getResultType() instanceof Union
|
||||
)
|
||||
or
|
||||
// Unary instructions tend to preserve enough information in practice that we
|
||||
// want taint to flow through.
|
||||
// The exception is `FieldAddressInstruction`. Together with the rule for
|
||||
@@ -195,11 +215,28 @@ private predicate instructionTaintStep(Instruction i1, Instruction i2) {
|
||||
// unrelated field. This would happen across function boundaries, where the IR
|
||||
// would not be able to match loads to stores.
|
||||
i2.(UnaryInstruction).getUnary() = i1 and
|
||||
not i2 instanceof FieldAddressInstruction
|
||||
(
|
||||
not i2 instanceof FieldAddressInstruction
|
||||
or
|
||||
i2.(FieldAddressInstruction).getField().getDeclaringType() instanceof Union
|
||||
)
|
||||
or
|
||||
i2.(ChiInstruction).getPartial() = i1 and
|
||||
// Flow out of definition-by-reference
|
||||
i2.(ChiInstruction).getPartial() = i1.(WriteSideEffectInstruction) and
|
||||
not i2.isResultConflated()
|
||||
or
|
||||
// Flow from an element to an array or union that contains it.
|
||||
i2.(ChiInstruction).getPartial() = i1 and
|
||||
not i2.isResultConflated() and
|
||||
exists(Type t | i2.getResultLanguageType().hasType(t, false) |
|
||||
t instanceof Union
|
||||
or
|
||||
t instanceof ArrayType
|
||||
or
|
||||
// Buffers of unknown size
|
||||
t instanceof UnknownType
|
||||
)
|
||||
or
|
||||
exists(BinaryInstruction bin |
|
||||
bin = i2 and
|
||||
predictableInstruction(i2.getAnOperand().getDef()) and
|
||||
@@ -356,6 +393,16 @@ private Element adjustedSink(DataFlow::Node sink) {
|
||||
result.(AssignOperation).getAnOperand() = sink.asExpr()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `tainted` may contain taint from `source`.
|
||||
*
|
||||
* A tainted expression is either directly user input, or is
|
||||
* computed from user input in a way that users can probably
|
||||
* control the exact output of the computation.
|
||||
*
|
||||
* This doesn't include data flow through global variables.
|
||||
* If you need that you must call `taintedIncludingGlobalVars`.
|
||||
*/
|
||||
cached
|
||||
predicate tainted(Expr source, Element tainted) {
|
||||
exists(DefaultTaintTrackingCfg cfg, DataFlow::Node sink |
|
||||
@@ -364,6 +411,21 @@ predicate tainted(Expr source, Element tainted) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `tainted` may contain taint from `source`, where the taint passed
|
||||
* through a global variable named `globalVar`.
|
||||
*
|
||||
* A tainted expression is either directly user input, or is
|
||||
* computed from user input in a way that users can probably
|
||||
* control the exact output of the computation.
|
||||
*
|
||||
* This version gives the same results as tainted but also includes
|
||||
* data flow through global variables.
|
||||
*
|
||||
* The parameter `globalVar` is the qualified name of the last global variable
|
||||
* used to move the value from source to tainted. If the taint did not pass
|
||||
* through a global variable, then `globalVar = ""`.
|
||||
*/
|
||||
cached
|
||||
predicate taintedIncludingGlobalVars(Expr source, Element tainted, string globalVar) {
|
||||
tainted(source, tainted) and
|
||||
@@ -381,11 +443,245 @@ predicate taintedIncludingGlobalVars(Expr source, Element tainted, string global
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the global variable whose qualified name is `id`. Use this predicate
|
||||
* together with `taintedIncludingGlobalVars`. Example:
|
||||
*
|
||||
* ```
|
||||
* exists(string varName |
|
||||
* taintedIncludingGlobalVars(source, tainted, varName) and
|
||||
* var = globalVarFromId(varName)
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
GlobalOrNamespaceVariable globalVarFromId(string id) { id = result.getQualifiedName() }
|
||||
|
||||
/**
|
||||
* Resolve potential target function(s) for `call`.
|
||||
*
|
||||
* If `call` is a call through a function pointer (`ExprCall`) or
|
||||
* targets a virtual method, simple data flow analysis is performed
|
||||
* in order to identify target(s).
|
||||
*/
|
||||
Function resolveCall(Call call) {
|
||||
exists(CallInstruction callInstruction |
|
||||
callInstruction.getAST() = call and
|
||||
result = Dispatch::viableCallable(callInstruction)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides definitions for augmenting source/sink pairs with data-flow paths
|
||||
* between them. From a `@kind path-problem` query, import this module in the
|
||||
* global scope, extend `TaintTrackingConfiguration`, and use `taintedWithPath`
|
||||
* in place of `tainted`.
|
||||
*
|
||||
* Importing this module will also import the query predicates that contain the
|
||||
* taint paths.
|
||||
*/
|
||||
module TaintedWithPath {
|
||||
private newtype TSingleton = MkSingleton()
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration that matches sources and sinks in the same
|
||||
* way as the `tainted` predicate.
|
||||
*
|
||||
* Override `isSink` and `taintThroughGlobals` as needed, but do not provide
|
||||
* a characteristic predicate.
|
||||
*/
|
||||
class TaintTrackingConfiguration extends TSingleton {
|
||||
/** Override this to specify which elements are sinks in this configuration. */
|
||||
abstract predicate isSink(Element e);
|
||||
|
||||
/**
|
||||
* Override this predicate to `any()` to allow taint to flow through global
|
||||
* variables.
|
||||
*/
|
||||
predicate taintThroughGlobals() { none() }
|
||||
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() { result = "TaintTrackingConfiguration" }
|
||||
}
|
||||
|
||||
private class AdjustedConfiguration extends DataFlow3::Configuration {
|
||||
AdjustedConfiguration() { this = "AdjustedConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source = getNodeForSource(_) }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
exists(TaintTrackingConfiguration cfg | cfg.isSink(adjustedSink(sink)))
|
||||
}
|
||||
|
||||
override predicate isAdditionalFlowStep(DataFlow::Node n1, DataFlow::Node n2) {
|
||||
instructionTaintStep(n1.asInstruction(), n2.asInstruction())
|
||||
or
|
||||
exists(TaintTrackingConfiguration cfg | cfg.taintThroughGlobals() |
|
||||
writesVariable(n1.asInstruction(), n2.asVariable().(GlobalOrNamespaceVariable))
|
||||
or
|
||||
readsVariable(n2.asInstruction(), n1.asVariable().(GlobalOrNamespaceVariable))
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isBarrier(DataFlow::Node node) { nodeIsBarrier(node) }
|
||||
|
||||
override predicate isBarrierIn(DataFlow::Node node) { nodeIsBarrierIn(node) }
|
||||
}
|
||||
|
||||
/*
|
||||
* A sink `Element` may map to multiple `DataFlowX::PathNode`s via (the
|
||||
* inverse of) `adjustedSink`. For example, an `Expr` maps to all its
|
||||
* conversions, and a `Variable` maps to all loads and stores from it. Because
|
||||
* the path node is part of the tuple that constitutes the alert, this leads
|
||||
* to duplicate alerts.
|
||||
*
|
||||
* To avoid showing duplicates, we edit the graph to replace the final node
|
||||
* coming from the data-flow library with a node that matches exactly the
|
||||
* `Element` sink that's requested.
|
||||
*
|
||||
* The same is done for sources.
|
||||
*/
|
||||
|
||||
private newtype TPathNode =
|
||||
TWrapPathNode(DataFlow3::PathNode n) or
|
||||
// There's a single newtype constructor for both sources and sinks since
|
||||
// that makes it easiest to deal with the case where source = sink.
|
||||
TEndpointPathNode(Element e) {
|
||||
exists(AdjustedConfiguration cfg, DataFlow3::Node sourceNode, DataFlow3::Node sinkNode |
|
||||
cfg.hasFlow(sourceNode, sinkNode)
|
||||
|
|
||||
sourceNode = getNodeForSource(e)
|
||||
or
|
||||
e = adjustedSink(sinkNode) and
|
||||
exists(TaintTrackingConfiguration ttCfg | ttCfg.isSink(e))
|
||||
)
|
||||
}
|
||||
|
||||
/** An opaque type used for the nodes of a data-flow path. */
|
||||
class PathNode extends TPathNode {
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() { none() }
|
||||
|
||||
/**
|
||||
* 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://help.semmle.com/QL/learn-ql/ql/locations.html).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
none()
|
||||
}
|
||||
}
|
||||
|
||||
private class WrapPathNode extends PathNode, TWrapPathNode {
|
||||
DataFlow3::PathNode inner() { this = TWrapPathNode(result) }
|
||||
|
||||
override string toString() { result = this.inner().toString() }
|
||||
|
||||
override predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
this.inner().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
}
|
||||
|
||||
private class EndpointPathNode extends PathNode, TEndpointPathNode {
|
||||
Expr inner() { this = TEndpointPathNode(result) }
|
||||
|
||||
override string toString() { result = this.inner().toString() }
|
||||
|
||||
override predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
this
|
||||
.inner()
|
||||
.getLocation()
|
||||
.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
}
|
||||
|
||||
/** A PathNode whose `Element` is a source. It may also be a sink. */
|
||||
private class InitialPathNode extends EndpointPathNode {
|
||||
InitialPathNode() { exists(getNodeForSource(this.inner())) }
|
||||
}
|
||||
|
||||
/** A PathNode whose `Element` is a sink. It may also be a source. */
|
||||
private class FinalPathNode extends EndpointPathNode {
|
||||
FinalPathNode() { exists(TaintTrackingConfiguration cfg | cfg.isSink(this.inner())) }
|
||||
}
|
||||
|
||||
/** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
|
||||
query predicate edges(PathNode a, PathNode b) {
|
||||
DataFlow3::PathGraph::edges(a.(WrapPathNode).inner(), b.(WrapPathNode).inner())
|
||||
or
|
||||
// To avoid showing trivial-looking steps, we _replace_ the last node instead
|
||||
// of adding an edge out of it.
|
||||
exists(WrapPathNode sinkNode |
|
||||
DataFlow3::PathGraph::edges(a.(WrapPathNode).inner(), sinkNode.inner()) and
|
||||
b.(FinalPathNode).inner() = adjustedSink(sinkNode.inner().getNode())
|
||||
)
|
||||
or
|
||||
// Same for the first node
|
||||
exists(WrapPathNode sourceNode |
|
||||
DataFlow3::PathGraph::edges(sourceNode.inner(), b.(WrapPathNode).inner()) and
|
||||
sourceNode.inner().getNode() = getNodeForSource(a.(InitialPathNode).inner())
|
||||
)
|
||||
or
|
||||
// Finally, handle the case where the path goes directly from a source to a
|
||||
// sink, meaning that they both need to be translated.
|
||||
exists(WrapPathNode sinkNode, WrapPathNode sourceNode |
|
||||
DataFlow3::PathGraph::edges(sourceNode.inner(), sinkNode.inner()) and
|
||||
sourceNode.inner().getNode() = getNodeForSource(a.(InitialPathNode).inner()) and
|
||||
b.(FinalPathNode).inner() = adjustedSink(sinkNode.inner().getNode())
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `n` is a node in the graph of data flow path explanations. */
|
||||
query predicate nodes(PathNode n, string key, string val) {
|
||||
key = "semmle.label" and val = n.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `tainted` may contain taint from `source`, where `sourceNode` and
|
||||
* `sinkNode` are the corresponding `PathNode`s that can be used in a query
|
||||
* to provide path explanations. Extend `TaintTrackingConfiguration` to use
|
||||
* this predicate.
|
||||
*
|
||||
* A tainted expression is either directly user input, or is computed from
|
||||
* user input in a way that users can probably control the exact output of
|
||||
* the computation.
|
||||
*/
|
||||
predicate taintedWithPath(Expr source, Element tainted, PathNode sourceNode, PathNode sinkNode) {
|
||||
exists(AdjustedConfiguration cfg, DataFlow3::Node flowSource, DataFlow3::Node flowSink |
|
||||
source = sourceNode.(InitialPathNode).inner() and
|
||||
flowSource = getNodeForSource(source) and
|
||||
cfg.hasFlow(flowSource, flowSink) and
|
||||
tainted = adjustedSink(flowSink) and
|
||||
tainted = sinkNode.(FinalPathNode).inner()
|
||||
)
|
||||
}
|
||||
|
||||
private predicate isGlobalVariablePathNode(WrapPathNode n) {
|
||||
n.inner().getNode().asVariable() instanceof GlobalOrNamespaceVariable
|
||||
}
|
||||
|
||||
private predicate edgesWithoutGlobals(PathNode a, PathNode b) {
|
||||
edges(a, b) and
|
||||
not isGlobalVariablePathNode(a) and
|
||||
not isGlobalVariablePathNode(b)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `tainted` can be reached from a taint source without passing
|
||||
* through a global variable.
|
||||
*/
|
||||
predicate taintedWithoutGlobals(Element tainted) {
|
||||
exists(PathNode sourceNode, FinalPathNode sinkNode |
|
||||
sourceNode.(WrapPathNode).inner().getNode() = getNodeForSource(_) and
|
||||
edgesWithoutGlobals+(sourceNode, sinkNode) and
|
||||
tainted = sinkNode.inner()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +70,7 @@ private module VirtualDispatch {
|
||||
// Call return
|
||||
exists(DataFlowCall call, ReturnKind returnKind |
|
||||
other = getAnOutNode(call, returnKind) and
|
||||
src.(ReturnNode).getKind() = returnKind and
|
||||
call.getStaticCallTarget() = src.getEnclosingCallable()
|
||||
returnNodeWithKindAndEnclosingCallable(src, returnKind, call.getStaticCallTarget())
|
||||
) and
|
||||
allowFromArg = false
|
||||
or
|
||||
@@ -125,6 +124,18 @@ private module VirtualDispatch {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A ReturnNode with its ReturnKind and its enclosing callable.
|
||||
*
|
||||
* Used to fix a join ordering issue in flowsFrom.
|
||||
*/
|
||||
private predicate returnNodeWithKindAndEnclosingCallable(
|
||||
ReturnNode node, ReturnKind kind, DataFlowCallable callable
|
||||
) {
|
||||
node.getKind() = kind and
|
||||
node.getEnclosingCallable() = callable
|
||||
}
|
||||
|
||||
/** Call through a function pointer. */
|
||||
private class DataSensitiveExprCall extends DataSensitiveCall {
|
||||
DataSensitiveExprCall() { not exists(this.getStaticCallTarget()) }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -26,13 +26,30 @@ private module Cached {
|
||||
)
|
||||
}
|
||||
|
||||
/** Provides predicates for calculating flow-through summaries. */
|
||||
pragma[nomagic]
|
||||
private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) {
|
||||
viableCallable(call) = result.getCallable() and
|
||||
kind = result.getKind()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a value at return position `pos` can be returned to `out` via `call`,
|
||||
* taking virtual dispatch into account.
|
||||
*/
|
||||
cached
|
||||
predicate viableReturnPosOut(DataFlowCall call, ReturnPosition pos, Node out) {
|
||||
exists(ReturnKindExt kind |
|
||||
pos = viableReturnPos(call, kind) and
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
/** Provides predicates for calculating flow-through summaries. */
|
||||
private module FlowThrough {
|
||||
/**
|
||||
* The first flow-through approximation:
|
||||
*
|
||||
* - Input/output access paths are abstracted with a Boolean parameter
|
||||
* - Input access paths are abstracted with a Boolean parameter
|
||||
* that indicates (non-)emptiness.
|
||||
*/
|
||||
private module Cand {
|
||||
@@ -40,83 +57,47 @@ private module Cached {
|
||||
* Holds if `p` can flow to `node` in the same callable using only
|
||||
* value-preserving steps.
|
||||
*
|
||||
* `read` indicates whether it is contents of `p` that can flow to `node`,
|
||||
* and `stored` indicates whether it flows to contents of `node`.
|
||||
* `read` indicates whether it is contents of `p` that can flow to `node`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate parameterValueFlowCand(
|
||||
ParameterNode p, Node node, boolean read, boolean stored
|
||||
) {
|
||||
private predicate parameterValueFlowCand(ParameterNode p, Node node, boolean read) {
|
||||
p = node and
|
||||
read = false and
|
||||
stored = false
|
||||
read = false
|
||||
or
|
||||
// local flow
|
||||
exists(Node mid |
|
||||
parameterValueFlowCand(p, mid, read, stored) and
|
||||
parameterValueFlowCand(p, mid, read) and
|
||||
simpleLocalFlowStep(mid, node)
|
||||
)
|
||||
or
|
||||
// read
|
||||
exists(Node mid, boolean readMid, boolean storedMid |
|
||||
parameterValueFlowCand(p, mid, readMid, storedMid) and
|
||||
readStep(mid, _, node) and
|
||||
stored = false
|
||||
|
|
||||
// value neither read nor stored prior to read
|
||||
readMid = false and
|
||||
storedMid = false and
|
||||
read = true
|
||||
or
|
||||
// value (possibly read and then) stored prior to read (same content)
|
||||
read = readMid and
|
||||
storedMid = true
|
||||
)
|
||||
or
|
||||
// store
|
||||
exists(Node mid |
|
||||
parameterValueFlowCand(p, mid, read, false) and
|
||||
storeStep(mid, _, node) and
|
||||
stored = true
|
||||
parameterValueFlowCand(p, mid, false) and
|
||||
readStep(mid, _, node) and
|
||||
read = true
|
||||
)
|
||||
or
|
||||
// flow through: no prior read or store
|
||||
// flow through: no prior read
|
||||
exists(ArgumentNode arg |
|
||||
parameterValueFlowArgCand(p, arg, false, false) and
|
||||
argumentValueFlowsThroughCand(arg, node, read, stored)
|
||||
parameterValueFlowArgCand(p, arg, false) and
|
||||
argumentValueFlowsThroughCand(arg, node, read)
|
||||
)
|
||||
or
|
||||
// flow through: no read or store inside method
|
||||
// flow through: no read inside method
|
||||
exists(ArgumentNode arg |
|
||||
parameterValueFlowArgCand(p, arg, read, stored) and
|
||||
argumentValueFlowsThroughCand(arg, node, false, false)
|
||||
)
|
||||
or
|
||||
// flow through: possible prior read and prior store with compatible
|
||||
// flow-through method
|
||||
exists(ArgumentNode arg, boolean mid |
|
||||
parameterValueFlowArgCand(p, arg, read, mid) and
|
||||
argumentValueFlowsThroughCand(arg, node, mid, stored)
|
||||
parameterValueFlowArgCand(p, arg, read) and
|
||||
argumentValueFlowsThroughCand(arg, node, false)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate parameterValueFlowArgCand(
|
||||
ParameterNode p, ArgumentNode arg, boolean read, boolean stored
|
||||
) {
|
||||
parameterValueFlowCand(p, arg, read, stored)
|
||||
private predicate parameterValueFlowArgCand(ParameterNode p, ArgumentNode arg, boolean read) {
|
||||
parameterValueFlowCand(p, arg, read)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate parameterValueFlowsToPreUpdateCand(ParameterNode p, PostUpdateNode n) {
|
||||
parameterValueFlowCand(p, n.getPreUpdateNode(), false, false)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate parameterValueFlowsToPostUpdateCand(
|
||||
ParameterNode p, PostUpdateNode n, boolean read
|
||||
) {
|
||||
parameterValueFlowCand(p, n, read, true)
|
||||
parameterValueFlowCand(p, n.getPreUpdateNode(), false)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,33 +106,21 @@ private module Cached {
|
||||
* into account.
|
||||
*
|
||||
* `read` indicates whether it is contents of `p` that can flow to the return
|
||||
* node, and `stored` indicates whether it flows to contents of the return
|
||||
* node.
|
||||
*/
|
||||
predicate parameterValueFlowReturnCand(
|
||||
ParameterNode p, ReturnKindExt kind, boolean read, boolean stored
|
||||
) {
|
||||
predicate parameterValueFlowReturnCand(ParameterNode p, ReturnKind kind, boolean read) {
|
||||
exists(ReturnNode ret |
|
||||
parameterValueFlowCand(p, ret, read, stored) and
|
||||
kind = TValueReturn(ret.getKind())
|
||||
)
|
||||
or
|
||||
exists(ParameterNode p2, int pos2, PostUpdateNode n |
|
||||
parameterValueFlowsToPostUpdateCand(p, n, read) and
|
||||
parameterValueFlowsToPreUpdateCand(p2, n) and
|
||||
p2.isParameterOf(_, pos2) and
|
||||
kind = TParamUpdate(pos2) and
|
||||
p != p2 and
|
||||
stored = true
|
||||
parameterValueFlowCand(p, ret, read) and
|
||||
kind = ret.getKind()
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate argumentValueFlowsThroughCand0(
|
||||
DataFlowCall call, ArgumentNode arg, ReturnKindExt kind, boolean read, boolean stored
|
||||
DataFlowCall call, ArgumentNode arg, ReturnKind kind, boolean read
|
||||
) {
|
||||
exists(ParameterNode param | viableParamArg(call, param, arg) |
|
||||
parameterValueFlowReturnCand(param, kind, read, stored)
|
||||
parameterValueFlowReturnCand(param, kind, read)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -159,22 +128,19 @@ private module Cached {
|
||||
* Holds if `arg` flows to `out` through a call using only value-preserving steps,
|
||||
* not taking call contexts into account.
|
||||
*
|
||||
* `read` indicates whether it is contents of `arg` that can flow to `out`, and
|
||||
* `stored` indicates whether it flows to contents of `out`.
|
||||
* `read` indicates whether it is contents of `arg` that can flow to `out`.
|
||||
*/
|
||||
predicate argumentValueFlowsThroughCand(
|
||||
ArgumentNode arg, Node out, boolean read, boolean stored
|
||||
) {
|
||||
exists(DataFlowCall call, ReturnKindExt kind |
|
||||
argumentValueFlowsThroughCand0(call, arg, kind, read, stored) and
|
||||
out = kind.getAnOutNode(call)
|
||||
predicate argumentValueFlowsThroughCand(ArgumentNode arg, Node out, boolean read) {
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
argumentValueFlowsThroughCand0(call, arg, kind, read) and
|
||||
out = getAnOutNode(call, kind)
|
||||
)
|
||||
}
|
||||
|
||||
predicate cand(ParameterNode p, Node n) {
|
||||
parameterValueFlowCand(p, n, _, _) and
|
||||
parameterValueFlowCand(p, n, _) and
|
||||
(
|
||||
parameterValueFlowReturnCand(p, _, _, _)
|
||||
parameterValueFlowReturnCand(p, _, _)
|
||||
or
|
||||
parameterValueFlowsToPreUpdateCand(p, _)
|
||||
)
|
||||
@@ -187,7 +153,6 @@ private module Cached {
|
||||
(
|
||||
n instanceof ParameterNode or
|
||||
n instanceof OutNode or
|
||||
n instanceof PostUpdateNode or
|
||||
readStep(_, _, n) or
|
||||
n instanceof CastNode
|
||||
)
|
||||
@@ -200,10 +165,6 @@ private module Cached {
|
||||
or
|
||||
n instanceof ReturnNode
|
||||
or
|
||||
Cand::parameterValueFlowsToPreUpdateCand(_, n)
|
||||
or
|
||||
storeStep(n, _, _)
|
||||
or
|
||||
readStep(n, _, _)
|
||||
or
|
||||
n instanceof CastNode
|
||||
@@ -237,230 +198,140 @@ private module Cached {
|
||||
/**
|
||||
* The final flow-through calculation:
|
||||
*
|
||||
* - Input/output access paths are abstracted with a `ContentOption` parameter
|
||||
* - Input access paths are abstracted with a `ContentOption` parameter
|
||||
* that represents the head of the access path. `TContentNone()` means that
|
||||
* the access path is unrestricted.
|
||||
* - Types are checked using the `compatibleTypes()` relation.
|
||||
*/
|
||||
cached
|
||||
private module Final {
|
||||
/**
|
||||
* Holds if `p` can flow to `node` in the same callable using only
|
||||
* value-preserving steps, not taking call contexts into account.
|
||||
*
|
||||
* `contentIn` describes the content of `p` that can flow to `node`
|
||||
* (if any), and `contentOut` describes the content of `node` that
|
||||
* it flows to (if any).
|
||||
* (if any).
|
||||
*/
|
||||
private predicate parameterValueFlow(
|
||||
ParameterNode p, Node node, ContentOption contentIn, ContentOption contentOut
|
||||
) {
|
||||
parameterValueFlow0(p, node, contentIn, contentOut) and
|
||||
predicate parameterValueFlow(ParameterNode p, Node node, ContentOption contentIn) {
|
||||
parameterValueFlow0(p, node, contentIn) and
|
||||
if node instanceof CastingNode
|
||||
then
|
||||
// normal flow through
|
||||
contentIn = TContentNone() and
|
||||
contentOut = TContentNone() and
|
||||
compatibleTypes(getErasedNodeTypeBound(p), getErasedNodeTypeBound(node))
|
||||
or
|
||||
// getter
|
||||
exists(Content fIn |
|
||||
contentIn.getContent() = fIn and
|
||||
contentOut = TContentNone() and
|
||||
compatibleTypes(fIn.getType(), getErasedNodeTypeBound(node))
|
||||
)
|
||||
or
|
||||
// (getter+)setter
|
||||
exists(Content fOut |
|
||||
contentOut.getContent() = fOut and
|
||||
compatibleTypes(fOut.getContainerType(), getErasedNodeTypeBound(node))
|
||||
)
|
||||
else any()
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate parameterValueFlow0(
|
||||
ParameterNode p, Node node, ContentOption contentIn, ContentOption contentOut
|
||||
) {
|
||||
private predicate parameterValueFlow0(ParameterNode p, Node node, ContentOption contentIn) {
|
||||
p = node and
|
||||
Cand::cand(p, _) and
|
||||
contentIn = TContentNone() and
|
||||
contentOut = TContentNone()
|
||||
contentIn = TContentNone()
|
||||
or
|
||||
// local flow
|
||||
exists(Node mid |
|
||||
parameterValueFlow(p, mid, contentIn, contentOut) and
|
||||
parameterValueFlow(p, mid, contentIn) and
|
||||
LocalFlowBigStep::localFlowBigStep(mid, node)
|
||||
)
|
||||
or
|
||||
// read
|
||||
exists(Node mid, Content f, ContentOption contentInMid, ContentOption contentOutMid |
|
||||
parameterValueFlow(p, mid, contentInMid, contentOutMid) and
|
||||
readStep(mid, f, node)
|
||||
|
|
||||
// value neither read nor stored prior to read
|
||||
contentInMid = TContentNone() and
|
||||
contentOutMid = TContentNone() and
|
||||
contentIn.getContent() = f and
|
||||
contentOut = TContentNone() and
|
||||
Cand::parameterValueFlowReturnCand(p, _, true, _) and
|
||||
compatibleTypes(getErasedNodeTypeBound(p), f.getContainerType())
|
||||
or
|
||||
// value (possibly read and then) stored prior to read (same content)
|
||||
contentIn = contentInMid and
|
||||
contentOutMid.getContent() = f and
|
||||
contentOut = TContentNone()
|
||||
)
|
||||
or
|
||||
// store
|
||||
exists(Node mid, Content f |
|
||||
parameterValueFlow(p, mid, contentIn, TContentNone()) and
|
||||
storeStep(mid, f, node) and
|
||||
contentOut.getContent() = f
|
||||
|
|
||||
contentIn = TContentNone() and
|
||||
compatibleTypes(getErasedNodeTypeBound(p), f.getType())
|
||||
or
|
||||
compatibleTypes(contentIn.getContent().getType(), f.getType())
|
||||
parameterValueFlow(p, mid, TContentNone()) and
|
||||
readStep(mid, f, node) and
|
||||
contentIn.getContent() = f and
|
||||
Cand::parameterValueFlowReturnCand(p, _, true) and
|
||||
compatibleTypes(getErasedNodeTypeBound(p), f.getContainerType())
|
||||
)
|
||||
or
|
||||
// flow through: no prior read or store
|
||||
// flow through: no prior read
|
||||
exists(ArgumentNode arg |
|
||||
parameterValueFlowArg(p, arg, TContentNone(), TContentNone()) and
|
||||
argumentValueFlowsThrough(_, arg, contentIn, contentOut, node)
|
||||
parameterValueFlowArg(p, arg, TContentNone()) and
|
||||
argumentValueFlowsThrough(arg, contentIn, node)
|
||||
)
|
||||
or
|
||||
// flow through: no read or store inside method
|
||||
// flow through: no read inside method
|
||||
exists(ArgumentNode arg |
|
||||
parameterValueFlowArg(p, arg, contentIn, contentOut) and
|
||||
argumentValueFlowsThrough(_, arg, TContentNone(), TContentNone(), node)
|
||||
)
|
||||
or
|
||||
// flow through: possible prior read and prior store with compatible
|
||||
// flow-through method
|
||||
exists(ArgumentNode arg, ContentOption contentMid |
|
||||
parameterValueFlowArg(p, arg, contentIn, contentMid) and
|
||||
argumentValueFlowsThrough(_, arg, contentMid, contentOut, node)
|
||||
parameterValueFlowArg(p, arg, contentIn) and
|
||||
argumentValueFlowsThrough(arg, TContentNone(), node)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate parameterValueFlowArg(
|
||||
ParameterNode p, ArgumentNode arg, ContentOption contentIn, ContentOption contentOut
|
||||
ParameterNode p, ArgumentNode arg, ContentOption contentIn
|
||||
) {
|
||||
parameterValueFlow(p, arg, contentIn, contentOut) and
|
||||
Cand::argumentValueFlowsThroughCand(arg, _, _, _)
|
||||
parameterValueFlow(p, arg, contentIn) and
|
||||
Cand::argumentValueFlowsThroughCand(arg, _, _)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate argumentValueFlowsThrough0(
|
||||
DataFlowCall call, ArgumentNode arg, ReturnKindExt kind, ContentOption contentIn,
|
||||
ContentOption contentOut
|
||||
DataFlowCall call, ArgumentNode arg, ReturnKind kind, ContentOption contentIn
|
||||
) {
|
||||
exists(ParameterNode param | viableParamArg(call, param, arg) |
|
||||
parameterValueFlowReturn(param, _, kind, contentIn, contentOut)
|
||||
parameterValueFlowReturn(param, kind, contentIn)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `arg` flows to `out` through `call` using only value-preserving steps,
|
||||
* Holds if `arg` flows to `out` through a call using only value-preserving steps,
|
||||
* not taking call contexts into account.
|
||||
*
|
||||
* `contentIn` describes the content of `arg` that can flow to `out` (if any), and
|
||||
* `contentOut` describes the content of `out` that it flows to (if any).
|
||||
* `contentIn` describes the content of `arg` that can flow to `out` (if any).
|
||||
*/
|
||||
cached
|
||||
predicate argumentValueFlowsThrough(
|
||||
DataFlowCall call, ArgumentNode arg, ContentOption contentIn, ContentOption contentOut,
|
||||
Node out
|
||||
) {
|
||||
exists(ReturnKindExt kind |
|
||||
argumentValueFlowsThrough0(call, arg, kind, contentIn, contentOut) and
|
||||
out = kind.getAnOutNode(call)
|
||||
pragma[nomagic]
|
||||
predicate argumentValueFlowsThrough(ArgumentNode arg, ContentOption contentIn, Node out) {
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
argumentValueFlowsThrough0(call, arg, kind, contentIn) and
|
||||
out = getAnOutNode(call, kind)
|
||||
|
|
||||
// normal flow through
|
||||
contentIn = TContentNone() and
|
||||
contentOut = TContentNone() and
|
||||
compatibleTypes(getErasedNodeTypeBound(arg), getErasedNodeTypeBound(out))
|
||||
or
|
||||
// getter
|
||||
exists(Content fIn |
|
||||
contentIn.getContent() = fIn and
|
||||
contentOut = TContentNone() and
|
||||
compatibleTypes(getErasedNodeTypeBound(arg), fIn.getContainerType()) and
|
||||
compatibleTypes(fIn.getType(), getErasedNodeTypeBound(out))
|
||||
)
|
||||
or
|
||||
// setter
|
||||
exists(Content fOut |
|
||||
contentIn = TContentNone() and
|
||||
contentOut.getContent() = fOut and
|
||||
compatibleTypes(getErasedNodeTypeBound(arg), fOut.getType()) and
|
||||
compatibleTypes(fOut.getContainerType(), getErasedNodeTypeBound(out))
|
||||
)
|
||||
or
|
||||
// getter+setter
|
||||
exists(Content fIn, Content fOut |
|
||||
contentIn.getContent() = fIn and
|
||||
contentOut.getContent() = fOut and
|
||||
compatibleTypes(getErasedNodeTypeBound(arg), fIn.getContainerType()) and
|
||||
compatibleTypes(fOut.getContainerType(), getErasedNodeTypeBound(out))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `p` can flow to the pre-update node associated with post-update
|
||||
* node `n`, in the same callable, using only value-preserving steps.
|
||||
*/
|
||||
cached
|
||||
predicate parameterValueFlowsToPreUpdate(ParameterNode p, PostUpdateNode n) {
|
||||
parameterValueFlow(p, n.getPreUpdateNode(), TContentNone(), TContentNone())
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate parameterValueFlowsToPostUpdate(
|
||||
ParameterNode p, PostUpdateNode n, ContentOption contentIn, ContentOption contentOut
|
||||
) {
|
||||
parameterValueFlow(p, n, contentIn, contentOut) and
|
||||
contentOut.hasContent()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `p` can flow to a return node of kind `kind` in the same
|
||||
* callable using only value-preserving steps.
|
||||
*
|
||||
* `contentIn` describes the content of `p` that can flow to the return
|
||||
* node (if any), and `contentOut` describes the content of the return
|
||||
* node that it flows to (if any).
|
||||
* node (if any).
|
||||
*/
|
||||
cached
|
||||
predicate parameterValueFlowReturn(
|
||||
ParameterNode p, Node ret, ReturnKindExt kind, ContentOption contentIn,
|
||||
ContentOption contentOut
|
||||
private predicate parameterValueFlowReturn(
|
||||
ParameterNode p, ReturnKind kind, ContentOption contentIn
|
||||
) {
|
||||
ret =
|
||||
any(ReturnNode n |
|
||||
parameterValueFlow(p, n, contentIn, contentOut) and
|
||||
kind = TValueReturn(n.getKind())
|
||||
)
|
||||
or
|
||||
ret =
|
||||
any(PostUpdateNode n |
|
||||
exists(ParameterNode p2, int pos2 |
|
||||
parameterValueFlowsToPostUpdate(p, n, contentIn, contentOut) and
|
||||
parameterValueFlowsToPreUpdate(p2, n) and
|
||||
p2.isParameterOf(_, pos2) and
|
||||
kind = TParamUpdate(pos2) and
|
||||
p != p2
|
||||
)
|
||||
)
|
||||
exists(ReturnNode ret |
|
||||
parameterValueFlow(p, ret, contentIn) and
|
||||
kind = ret.getKind()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
import Final
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `p` can flow to the pre-update node associated with post-update
|
||||
* node `n`, in the same callable, using only value-preserving steps.
|
||||
*/
|
||||
cached
|
||||
predicate parameterValueFlowsToPreUpdate(ParameterNode p, PostUpdateNode n) {
|
||||
parameterValueFlow(p, n.getPreUpdateNode(), TContentNone())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data can flow from `node1` to `node2` via a direct assignment to
|
||||
* `f`.
|
||||
@@ -469,14 +340,14 @@ private module Cached {
|
||||
* been stored into, in order to handle cases like `x.f1.f2 = y`.
|
||||
*/
|
||||
cached
|
||||
predicate storeDirect(Node node1, Content f, Node node2) {
|
||||
predicate store(Node node1, Content f, Node node2) {
|
||||
storeStep(node1, f, node2) and readStep(_, f, _)
|
||||
or
|
||||
exists(Node n1, Node n2 |
|
||||
n1 = node1.(PostUpdateNode).getPreUpdateNode() and
|
||||
n2 = node2.(PostUpdateNode).getPreUpdateNode()
|
||||
|
|
||||
argumentValueFlowsThrough(_, n2, TContentSome(f), TContentNone(), n1)
|
||||
argumentValueFlowsThrough(n2, TContentSome(f), n1)
|
||||
or
|
||||
readStep(n2, f, n1)
|
||||
)
|
||||
@@ -520,6 +391,21 @@ private module Cached {
|
||||
newtype TReturnKindExt =
|
||||
TValueReturn(ReturnKind kind) or
|
||||
TParamUpdate(int pos) { exists(ParameterNode p | p.isParameterOf(_, pos)) }
|
||||
|
||||
cached
|
||||
newtype TBooleanOption =
|
||||
TBooleanNone() or
|
||||
TBooleanSome(boolean b) { b = true or b = false }
|
||||
|
||||
cached
|
||||
newtype TAccessPathFront =
|
||||
TFrontNil(DataFlowType t) or
|
||||
TFrontHead(Content f)
|
||||
|
||||
cached
|
||||
newtype TAccessPathFrontOption =
|
||||
TAccessPathFrontNone() or
|
||||
TAccessPathFrontSome(AccessPathFront apf)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -529,8 +415,7 @@ class CastingNode extends Node {
|
||||
CastingNode() {
|
||||
this instanceof ParameterNode or
|
||||
this instanceof CastNode or
|
||||
this instanceof OutNode or
|
||||
this.(PostUpdateNode).getPreUpdateNode() instanceof ArgumentNode
|
||||
this instanceof OutNodeExt
|
||||
}
|
||||
}
|
||||
|
||||
@@ -538,7 +423,7 @@ newtype TContentOption =
|
||||
TContentNone() or
|
||||
TContentSome(Content f)
|
||||
|
||||
class ContentOption extends TContentOption {
|
||||
private class ContentOption extends TContentOption {
|
||||
Content getContent() { this = TContentSome(result) }
|
||||
|
||||
predicate hasContent() { exists(this.getContent()) }
|
||||
@@ -678,6 +563,18 @@ class ReturnNodeExt extends Node {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A node to which data can flow from a call. Either an ordinary out node
|
||||
* or a post-update node associated with a call argument.
|
||||
*/
|
||||
class OutNodeExt extends Node {
|
||||
OutNodeExt() {
|
||||
this instanceof OutNode
|
||||
or
|
||||
this.(PostUpdateNode).getPreUpdateNode() instanceof ArgumentNode
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An extended return kind. A return kind describes how data can be returned
|
||||
* from a callable. This can either be through a returned value or an updated
|
||||
@@ -688,7 +585,7 @@ abstract class ReturnKindExt extends TReturnKindExt {
|
||||
abstract string toString();
|
||||
|
||||
/** Gets a node corresponding to data flow out of `call`. */
|
||||
abstract Node getAnOutNode(DataFlowCall call);
|
||||
abstract OutNodeExt getAnOutNode(DataFlowCall call);
|
||||
}
|
||||
|
||||
class ValueReturnKind extends ReturnKindExt, TValueReturn {
|
||||
@@ -700,7 +597,9 @@ class ValueReturnKind extends ReturnKindExt, TValueReturn {
|
||||
|
||||
override string toString() { result = kind.toString() }
|
||||
|
||||
override Node getAnOutNode(DataFlowCall call) { result = getAnOutNode(call, this.getKind()) }
|
||||
override OutNodeExt getAnOutNode(DataFlowCall call) {
|
||||
result = getAnOutNode(call, this.getKind())
|
||||
}
|
||||
}
|
||||
|
||||
class ParamUpdateReturnKind extends ReturnKindExt, TParamUpdate {
|
||||
@@ -712,9 +611,9 @@ class ParamUpdateReturnKind extends ReturnKindExt, TParamUpdate {
|
||||
|
||||
override string toString() { result = "param update " + pos }
|
||||
|
||||
override PostUpdateNode getAnOutNode(DataFlowCall call) {
|
||||
override OutNodeExt getAnOutNode(DataFlowCall call) {
|
||||
exists(ArgumentNode arg |
|
||||
result.getPreUpdateNode() = arg and
|
||||
result.(PostUpdateNode).getPreUpdateNode() = arg and
|
||||
arg.argumentOf(call, this.getPosition())
|
||||
)
|
||||
}
|
||||
@@ -779,77 +678,58 @@ DataFlowCallable resolveCall(DataFlowCall call, CallContext cc) {
|
||||
result = viableCallable(call) and cc instanceof CallContextReturn
|
||||
}
|
||||
|
||||
newtype TSummary =
|
||||
TSummaryVal() or
|
||||
TSummaryTaint() or
|
||||
TSummaryReadVal(Content f) or
|
||||
TSummaryReadTaint(Content f) or
|
||||
TSummaryTaintStore(Content f)
|
||||
|
||||
/**
|
||||
* A summary of flow through a callable. This can either be value-preserving
|
||||
* if no additional steps are used, taint-flow if at least one additional step
|
||||
* is used, or any one of those combined with a store or a read. Summaries
|
||||
* recorded at a return node are restricted to include at least one additional
|
||||
* step, as the value-based summaries are calculated independent of the
|
||||
* configuration.
|
||||
*/
|
||||
class Summary extends TSummary {
|
||||
string toString() {
|
||||
result = "Val" and this = TSummaryVal()
|
||||
or
|
||||
result = "Taint" and this = TSummaryTaint()
|
||||
or
|
||||
exists(Content f |
|
||||
result = "ReadVal " + f.toString() and this = TSummaryReadVal(f)
|
||||
or
|
||||
result = "ReadTaint " + f.toString() and this = TSummaryReadTaint(f)
|
||||
or
|
||||
result = "TaintStore " + f.toString() and this = TSummaryTaintStore(f)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the summary that results from extending this with an additional step. */
|
||||
Summary additionalStep() {
|
||||
this = TSummaryVal() and result = TSummaryTaint()
|
||||
or
|
||||
this = TSummaryTaint() and result = TSummaryTaint()
|
||||
or
|
||||
exists(Content f | this = TSummaryReadVal(f) and result = TSummaryReadTaint(f))
|
||||
or
|
||||
exists(Content f | this = TSummaryReadTaint(f) and result = TSummaryReadTaint(f))
|
||||
}
|
||||
|
||||
/** Gets the summary that results from extending this with a read. */
|
||||
Summary readStep(Content f) { this = TSummaryVal() and result = TSummaryReadVal(f) }
|
||||
|
||||
/** Gets the summary that results from extending this with a store. */
|
||||
Summary storeStep(Content f) { this = TSummaryTaint() and result = TSummaryTaintStore(f) }
|
||||
|
||||
/** Gets the summary that results from extending this with `step`. */
|
||||
bindingset[this, step]
|
||||
Summary compose(Summary step) {
|
||||
this = TSummaryVal() and result = step
|
||||
or
|
||||
this = TSummaryTaint() and
|
||||
(step = TSummaryTaint() or step = TSummaryTaintStore(_)) and
|
||||
result = step
|
||||
or
|
||||
exists(Content f |
|
||||
this = TSummaryReadVal(f) and step = TSummaryTaint() and result = TSummaryReadTaint(f)
|
||||
)
|
||||
or
|
||||
this = TSummaryReadTaint(_) and step = TSummaryTaint() and result = this
|
||||
}
|
||||
|
||||
/** Holds if this summary does not include any taint steps. */
|
||||
predicate isPartial() {
|
||||
this = TSummaryVal() or
|
||||
this = TSummaryReadVal(_)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
DataFlowType getErasedNodeTypeBound(Node n) { result = getErasedRepr(n.getTypeBound()) }
|
||||
|
||||
predicate readDirect = readStep/3;
|
||||
predicate read = readStep/3;
|
||||
|
||||
/** An optional Boolean value. */
|
||||
class BooleanOption extends TBooleanOption {
|
||||
string toString() {
|
||||
this = TBooleanNone() and result = "<none>"
|
||||
or
|
||||
this = TBooleanSome(any(boolean b | result = b.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The front of an access path. This is either a head or a nil.
|
||||
*/
|
||||
abstract class AccessPathFront extends TAccessPathFront {
|
||||
abstract string toString();
|
||||
|
||||
abstract DataFlowType getType();
|
||||
|
||||
abstract boolean toBoolNonEmpty();
|
||||
|
||||
predicate headUsesContent(Content f) { this = TFrontHead(f) }
|
||||
}
|
||||
|
||||
class AccessPathFrontNil extends AccessPathFront, TFrontNil {
|
||||
override string toString() {
|
||||
exists(DataFlowType t | this = TFrontNil(t) | result = ppReprType(t))
|
||||
}
|
||||
|
||||
override DataFlowType getType() { this = TFrontNil(result) }
|
||||
|
||||
override boolean toBoolNonEmpty() { result = false }
|
||||
}
|
||||
|
||||
class AccessPathFrontHead extends AccessPathFront, TFrontHead {
|
||||
override string toString() { exists(Content f | this = TFrontHead(f) | result = f.toString()) }
|
||||
|
||||
override DataFlowType getType() {
|
||||
exists(Content head | this = TFrontHead(head) | result = head.getContainerType())
|
||||
}
|
||||
|
||||
override boolean toBoolNonEmpty() { result = true }
|
||||
}
|
||||
|
||||
/** An optional access path front. */
|
||||
class AccessPathFrontOption extends TAccessPathFrontOption {
|
||||
string toString() {
|
||||
this = TAccessPathFrontNone() and result = "<none>"
|
||||
or
|
||||
this = TAccessPathFrontSome(any(AccessPathFront apf | result = apf.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,9 @@ class ArgumentNode extends InstructionNode {
|
||||
DataFlowCall getCall() { this.argumentOf(result, _) }
|
||||
}
|
||||
|
||||
private newtype TReturnKind = TNormalReturnKind()
|
||||
private newtype TReturnKind =
|
||||
TNormalReturnKind() or
|
||||
TIndirectReturnKind(ParameterIndex index)
|
||||
|
||||
/**
|
||||
* A return kind. A return kind describes how a value can be returned
|
||||
@@ -44,23 +46,76 @@ private newtype TReturnKind = TNormalReturnKind()
|
||||
*/
|
||||
class ReturnKind extends TReturnKind {
|
||||
/** Gets a textual representation of this return kind. */
|
||||
string toString() { result = "return" }
|
||||
abstract string toString();
|
||||
}
|
||||
|
||||
private class NormalReturnKind extends ReturnKind, TNormalReturnKind {
|
||||
override string toString() { result = "return" }
|
||||
}
|
||||
|
||||
private class IndirectReturnKind extends ReturnKind, TIndirectReturnKind {
|
||||
ParameterIndex index;
|
||||
|
||||
IndirectReturnKind() { this = TIndirectReturnKind(index) }
|
||||
|
||||
override string toString() { result = "outparam[" + index.toString() + "]" }
|
||||
}
|
||||
|
||||
/** A data flow node that occurs as the result of a `ReturnStmt`. */
|
||||
class ReturnNode extends InstructionNode {
|
||||
ReturnNode() { exists(ReturnValueInstruction ret | this.getInstruction() = ret.getReturnValue()) }
|
||||
Instruction primary;
|
||||
|
||||
ReturnNode() {
|
||||
exists(ReturnValueInstruction ret | instr = ret.getReturnValue() and primary = ret)
|
||||
or
|
||||
exists(ReturnIndirectionInstruction rii |
|
||||
instr = rii.getSideEffectOperand().getAnyDef() and primary = rii
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the kind of this returned value. */
|
||||
ReturnKind getKind() { result = TNormalReturnKind() }
|
||||
abstract ReturnKind getKind();
|
||||
}
|
||||
|
||||
class ReturnValueNode extends ReturnNode {
|
||||
override ReturnValueInstruction primary;
|
||||
|
||||
override ReturnKind getKind() { result = TNormalReturnKind() }
|
||||
}
|
||||
|
||||
class ReturnIndirectionNode extends ReturnNode {
|
||||
override ReturnIndirectionInstruction primary;
|
||||
|
||||
override ReturnKind getKind() { result = TIndirectReturnKind(primary.getParameter().getIndex()) }
|
||||
}
|
||||
|
||||
/** A data flow node that represents the output of a call. */
|
||||
class OutNode extends InstructionNode {
|
||||
override CallInstruction instr;
|
||||
OutNode() {
|
||||
instr instanceof CallInstruction or
|
||||
instr instanceof WriteSideEffectInstruction
|
||||
}
|
||||
|
||||
/** Gets the underlying call. */
|
||||
DataFlowCall getCall() { result = instr }
|
||||
abstract DataFlowCall getCall();
|
||||
|
||||
abstract ReturnKind getReturnKind();
|
||||
}
|
||||
|
||||
private class CallOutNode extends OutNode {
|
||||
override CallInstruction instr;
|
||||
|
||||
override DataFlowCall getCall() { result = instr }
|
||||
|
||||
override ReturnKind getReturnKind() { result instanceof NormalReturnKind }
|
||||
}
|
||||
|
||||
private class SideEffectOutNode extends OutNode {
|
||||
override WriteSideEffectInstruction instr;
|
||||
|
||||
override DataFlowCall getCall() { result = instr.getPrimaryInstruction() }
|
||||
|
||||
override ReturnKind getReturnKind() { result = TIndirectReturnKind(instr.getIndex()) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,7 +124,7 @@ class OutNode extends InstructionNode {
|
||||
*/
|
||||
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) {
|
||||
result.getCall() = call and
|
||||
kind = TNormalReturnKind()
|
||||
result.getReturnKind() = kind
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,13 +192,32 @@ private class ArrayContent extends Content, TArrayContent {
|
||||
override Type getType() { none() }
|
||||
}
|
||||
|
||||
private predicate storeStepNoChi(Node node1, Content f, PostUpdateNode node2) {
|
||||
exists(FieldAddressInstruction fa, StoreInstruction store |
|
||||
store = node2.asInstruction() and
|
||||
store.getDestinationAddress() = fa and
|
||||
store.getSourceValue() = node1.asInstruction() and
|
||||
f.(FieldContent).getField() = fa.getField()
|
||||
)
|
||||
}
|
||||
|
||||
private predicate storeStepChi(Node node1, Content f, PostUpdateNode node2) {
|
||||
exists(FieldAddressInstruction fa, StoreInstruction store |
|
||||
node1.asInstruction() = store and
|
||||
store.getDestinationAddress() = fa and
|
||||
node2.asInstruction().(ChiInstruction).getPartial() = store and
|
||||
f.(FieldContent).getField() = fa.getField()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, Content f, PostUpdateNode node2) {
|
||||
none() // stub implementation
|
||||
storeStepNoChi(node1, f, node2) or
|
||||
storeStepChi(node1, f, node2)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,7 +226,12 @@ predicate storeStep(Node node1, Content f, PostUpdateNode node2) {
|
||||
* `node2`.
|
||||
*/
|
||||
predicate readStep(Node node1, Content f, Node node2) {
|
||||
none() // stub implementation
|
||||
exists(FieldAddressInstruction fa, LoadInstruction load |
|
||||
load.getSourceAddress() = fa and
|
||||
node1.asInstruction() = load.getSourceValueOperand().getAnyDef() and
|
||||
fa.getField() = f.(FieldContent).getField() and
|
||||
load = node2.asInstruction()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,7 +245,7 @@ Type getErasedRepr(Type t) {
|
||||
}
|
||||
|
||||
/** Gets a string representation of a type returned by `getErasedRepr`. */
|
||||
string ppReprType(Type t) { result = t.toString() }
|
||||
string ppReprType(Type t) { none() } // stub implementation
|
||||
|
||||
/**
|
||||
* Holds if `t1` and `t2` are compatible, that is, whether data can flow from
|
||||
|
||||
@@ -63,6 +63,18 @@ class Node extends TIRDataFlowNode {
|
||||
*/
|
||||
Variable asVariable() { result = this.(VariableNode).getVariable() }
|
||||
|
||||
/**
|
||||
* Gets the expression that is partially defined by this node, if any.
|
||||
*
|
||||
* Partial definitions are created for field stores (`x.y = taint();` is a partial
|
||||
* definition of `x`), and for calls that may change the value of an object (so
|
||||
* `x.set(taint())` is a partial definition of `x`, and `transfer(&x, taint())` is
|
||||
* a partial definition of `&x`).
|
||||
*/
|
||||
Expr asPartialDefinition() {
|
||||
result = this.(PartialDefinitionNode).getInstruction().getUnconvertedResultExpression()
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: See UninitializedNode.
|
||||
*
|
||||
@@ -96,6 +108,9 @@ class Node extends TIRDataFlowNode {
|
||||
string toString() { none() } // overridden by subclasses
|
||||
}
|
||||
|
||||
/**
|
||||
* An instruction, viewed as a node in a data flow graph.
|
||||
*/
|
||||
class InstructionNode extends Node, TInstructionNode {
|
||||
Instruction instr;
|
||||
|
||||
@@ -157,19 +172,20 @@ int getArgumentPosOfSideEffect(int index) {
|
||||
|
||||
/**
|
||||
* The value of a parameter at function entry, viewed as a node in a data
|
||||
* flow graph. This type includes implicit parameters.
|
||||
* flow graph. This includes both explicit parameters such as `x` in `f(x)`
|
||||
* and implicit parameters such as `this` in `x.f()`.
|
||||
*
|
||||
* To match a specific kind of parameter, consider using one of the subclasses
|
||||
* `ExplicitParameterNode`, `InstanceParameterNode`, or
|
||||
* `ExplicitParameterNode`, `ThisParameterNode`, or
|
||||
* `ParameterIndirectionNode`.
|
||||
*/
|
||||
class ParameterNode extends InstructionNode {
|
||||
ParameterNode() {
|
||||
// To avoid making this class abstract, we enumerate its values here
|
||||
instr instanceof InitializeThisInstruction
|
||||
or
|
||||
instr instanceof InitializeParameterInstruction
|
||||
or
|
||||
instr instanceof InitializeThisInstruction
|
||||
or
|
||||
instr instanceof InitializeIndirectionInstruction
|
||||
}
|
||||
|
||||
@@ -178,19 +194,7 @@ class ParameterNode extends InstructionNode {
|
||||
* implicit `this` parameter is considered to have position `-1`, and
|
||||
* pointer-indirection parameters are at further negative positions.
|
||||
*/
|
||||
predicate isParameterOf(Function f, int pos) { none() } // overridden in subclasses
|
||||
}
|
||||
|
||||
/** An implicit `this` parameter. */
|
||||
class InstanceParameterNode extends ParameterNode {
|
||||
override InitializeThisInstruction instr;
|
||||
|
||||
override predicate isParameterOf(Function f, int pos) {
|
||||
pos = -1 and
|
||||
instr.getEnclosingFunction() = f
|
||||
}
|
||||
|
||||
override string toString() { result = "this" }
|
||||
predicate isParameterOf(Function f, int pos) { none() } // overridden by subclasses
|
||||
}
|
||||
|
||||
/** An explicit positional parameter, not including `this` or `...`. */
|
||||
@@ -204,7 +208,18 @@ class ExplicitParameterNode extends ParameterNode {
|
||||
/** Gets the `Parameter` associated with this node. */
|
||||
Parameter getParameter() { result = instr.getParameter() }
|
||||
|
||||
override string toString() { result = this.getParameter().toString() }
|
||||
override string toString() { result = instr.getParameter().toString() }
|
||||
}
|
||||
|
||||
/** An implicit `this` parameter. */
|
||||
class ThisParameterNode extends ParameterNode {
|
||||
override InitializeThisInstruction instr;
|
||||
|
||||
override predicate isParameterOf(Function f, int pos) {
|
||||
pos = -1 and instr.getEnclosingFunction() = f
|
||||
}
|
||||
|
||||
override string toString() { result = "this" }
|
||||
}
|
||||
|
||||
/** A virtual parameter to model the pointed-to object of a pointer parameter. */
|
||||
@@ -262,6 +277,57 @@ abstract class PostUpdateNode extends InstructionNode {
|
||||
abstract Node getPreUpdateNode();
|
||||
}
|
||||
|
||||
/**
|
||||
* The base class for nodes that perform "partial definitions".
|
||||
*
|
||||
* In contrast to a normal "definition", which provides a new value for
|
||||
* something, a partial definition is an expression that may affect a
|
||||
* value, but does not necessarily replace it entirely. For example:
|
||||
* ```
|
||||
* x.y = 1; // a partial definition of the object `x`.
|
||||
* x.y.z = 1; // a partial definition of the object `x.y`.
|
||||
* x.setY(1); // a partial definition of the object `x`.
|
||||
* setY(&x); // a partial definition of the object `x`.
|
||||
* ```
|
||||
*/
|
||||
abstract private class PartialDefinitionNode extends PostUpdateNode, TInstructionNode { }
|
||||
|
||||
private class ExplicitFieldStoreQualifierNode extends PartialDefinitionNode {
|
||||
override ChiInstruction instr;
|
||||
|
||||
ExplicitFieldStoreQualifierNode() {
|
||||
not instr.isResultConflated() and
|
||||
exists(StoreInstruction store, FieldInstruction field |
|
||||
instr.getPartial() = store and field = store.getDestinationAddress()
|
||||
)
|
||||
}
|
||||
|
||||
// There might be multiple `ChiInstructions` that has a particular instruction as
|
||||
// the total operand - so this definition gives consistency errors in
|
||||
// DataFlowImplConsistency::Consistency. However, it's not clear what (if any) implications
|
||||
// this consistency failure has.
|
||||
override Node getPreUpdateNode() { result.asInstruction() = instr.getTotal() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Not every store instruction generates a chi instruction that we can attach a PostUpdateNode to.
|
||||
* For instance, an update to a field of a struct containing only one field. For these cases we
|
||||
* attach the PostUpdateNode to the store instruction. There's no obvious pre update node for this case
|
||||
* (as the entire memory is updated), so `getPreUpdateNode` is implemented as `none()`.
|
||||
*/
|
||||
private class ExplicitSingleFieldStoreQualifierNode extends PartialDefinitionNode {
|
||||
override StoreInstruction instr;
|
||||
|
||||
ExplicitSingleFieldStoreQualifierNode() {
|
||||
exists(FieldAddressInstruction field |
|
||||
field = instr.getDestinationAddress() and
|
||||
not exists(ChiInstruction chi | chi.getPartial() = instr)
|
||||
)
|
||||
}
|
||||
|
||||
override Node getPreUpdateNode() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A node that represents the value of a variable after a function call that
|
||||
* may have changed the variable because it's passed by reference.
|
||||
@@ -297,6 +363,17 @@ class DefinitionByReferenceNode extends InstructionNode {
|
||||
Parameter getParameter() {
|
||||
exists(CallInstruction ci | result = ci.getStaticCallTarget().getParameter(instr.getIndex()))
|
||||
}
|
||||
|
||||
override string toString() {
|
||||
// This string should be unique enough to be helpful but common enough to
|
||||
// avoid storing too many different strings.
|
||||
result =
|
||||
instr.getPrimaryInstruction().(CallInstruction).getStaticCallTarget().getName() +
|
||||
" output argument"
|
||||
or
|
||||
not exists(instr.getPrimaryInstruction().(CallInstruction).getStaticCallTarget()) and
|
||||
result = "output argument"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -335,6 +412,10 @@ class VariableNode extends Node, TVariableNode {
|
||||
*/
|
||||
InstructionNode instructionNode(Instruction instr) { result.getInstruction() = instr }
|
||||
|
||||
/**
|
||||
* Gets the `Node` corresponding to a definition by reference of the variable
|
||||
* that is passed as `argument` of a call.
|
||||
*/
|
||||
DefinitionByReferenceNode definitionByReferenceNode(Expr e) { result.getArgument() = e }
|
||||
|
||||
/**
|
||||
@@ -347,7 +428,7 @@ ExprNode exprNode(Expr e) { result.getExpr() = e }
|
||||
* Gets the `Node` corresponding to `e`, if any. Here, `e` may be a
|
||||
* `Conversion`.
|
||||
*/
|
||||
ExprNode convertedExprNode(Expr e) { result.getExpr() = e }
|
||||
ExprNode convertedExprNode(Expr e) { result.getConvertedExpr() = e }
|
||||
|
||||
/**
|
||||
* Gets the `Node` corresponding to the value of `p` at function entry.
|
||||
@@ -381,6 +462,16 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
simpleInstructionLocalFlowStep(nodeFrom.asInstruction(), nodeTo.asInstruction())
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate getFieldSizeOfClass(Class c, Type type, int size) {
|
||||
exists(Field f |
|
||||
f.getDeclaringType() = c and
|
||||
f.getType() = type and
|
||||
type.getSize() = size
|
||||
)
|
||||
}
|
||||
|
||||
cached
|
||||
private predicate simpleInstructionLocalFlowStep(Instruction iFrom, Instruction iTo) {
|
||||
iTo.(CopyInstruction).getSourceValue() = iFrom
|
||||
or
|
||||
@@ -424,6 +515,36 @@ private predicate simpleInstructionLocalFlowStep(Instruction iFrom, Instruction
|
||||
// for now.
|
||||
iTo.getAnOperand().(ChiTotalOperand).getDef() = iFrom
|
||||
or
|
||||
// The next two rules allow flow from partial definitions in setters to succeeding loads in the caller.
|
||||
// First, we add flow from write side-effects to non-conflated chi instructions through their
|
||||
// partial operands. Consider the following example:
|
||||
// ```
|
||||
// void setX(Point* p, int new_x) {
|
||||
// p->x = new_x;
|
||||
// }
|
||||
// ...
|
||||
// setX(&p, taint());
|
||||
// ```
|
||||
// Here, a `WriteSideEffectInstruction` will provide a new definition for `p->x` after the call to
|
||||
// `setX`, which will be melded into `p` through a chi instruction.
|
||||
iTo.getAnOperand().(ChiPartialOperand).getDef() = iFrom.(WriteSideEffectInstruction) and
|
||||
not iTo.isResultConflated()
|
||||
or
|
||||
// Next, we add flow from non-conflated chi instructions to loads (even when they are not precise).
|
||||
// This ensures that loads of `p->x` gets data flow from the `WriteSideEffectInstruction` above.
|
||||
exists(ChiInstruction chi | iFrom = chi |
|
||||
not chi.isResultConflated() and
|
||||
iTo.(LoadInstruction).getSourceValueOperand().getAnyDef() = chi
|
||||
)
|
||||
or
|
||||
// Flow from stores to structs with a single field to a load of that field.
|
||||
iTo.(LoadInstruction).getSourceValueOperand().getAnyDef() = iFrom and
|
||||
exists(int size, Type type |
|
||||
type = iFrom.getResultType() and
|
||||
iTo.getResultType().getSize() = size and
|
||||
getFieldSizeOfClass(iTo.getResultType(), type, size)
|
||||
)
|
||||
or
|
||||
// Flow through modeled functions
|
||||
modelFlow(iFrom, iTo)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Provides predicates for mapping the `FunctionInput` and `FunctionOutput`
|
||||
* classes used in function models to the corresponding instructions.
|
||||
*/
|
||||
|
||||
private import semmle.code.cpp.ir.IR
|
||||
private import semmle.code.cpp.ir.dataflow.DataFlow
|
||||
|
||||
/**
|
||||
* Gets the instruction that goes into `input` for `call`.
|
||||
*/
|
||||
Instruction callInput(CallInstruction call, FunctionInput input) {
|
||||
// A positional argument
|
||||
exists(int index |
|
||||
result = call.getPositionalArgument(index) and
|
||||
input.isParameter(index)
|
||||
)
|
||||
or
|
||||
// A value pointed to by a positional argument
|
||||
exists(ReadSideEffectInstruction read |
|
||||
result = read and
|
||||
read.getPrimaryInstruction() = call and
|
||||
input.isParameterDeref(read.getIndex())
|
||||
)
|
||||
or
|
||||
// The qualifier pointer
|
||||
result = call.getThisArgument() and
|
||||
input.isQualifierAddress()
|
||||
//TODO: qualifier deref
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the instruction that holds the `output` for `call`.
|
||||
*/
|
||||
Instruction callOutput(CallInstruction call, FunctionOutput output) {
|
||||
// The return value
|
||||
result = call and
|
||||
output.isReturnValue()
|
||||
or
|
||||
// The side effect of a call on the value pointed to by a positional argument
|
||||
exists(WriteSideEffectInstruction effect |
|
||||
result = effect and
|
||||
effect.getPrimaryInstruction() = call and
|
||||
output.isParameterDeref(effect.getIndex())
|
||||
)
|
||||
// TODO: qualifiers, return value dereference
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
private import semmle.code.cpp.ir.IR
|
||||
private import semmle.code.cpp.ir.dataflow.DataFlow
|
||||
private import ModelUtil
|
||||
private import semmle.code.cpp.models.interfaces.DataFlow
|
||||
private import semmle.code.cpp.models.interfaces.SideEffect
|
||||
|
||||
/**
|
||||
* Holds if taint propagates from `nodeFrom` to `nodeTo` in exactly one local
|
||||
@@ -45,6 +48,25 @@ private predicate localInstructionTaintStep(Instruction nodeFrom, Instruction no
|
||||
)
|
||||
or
|
||||
nodeTo.(LoadInstruction).getSourceAddress() = nodeFrom
|
||||
or
|
||||
modeledInstructionTaintStep(nodeFrom, nodeTo)
|
||||
or
|
||||
// Flow through partial reads of arrays and unions
|
||||
nodeTo.(LoadInstruction).getSourceValueOperand().getAnyDef() = nodeFrom and
|
||||
not nodeFrom.isResultConflated() and
|
||||
(
|
||||
nodeFrom.getResultType() instanceof ArrayType or
|
||||
nodeFrom.getResultType() instanceof Union
|
||||
)
|
||||
or
|
||||
// Flow from an element to an array or union that contains it.
|
||||
nodeTo.(ChiInstruction).getPartial() = nodeFrom and
|
||||
not nodeTo.isResultConflated() and
|
||||
exists(Type t | nodeTo.getResultLanguageType().hasType(t, false) |
|
||||
t instanceof Union
|
||||
or
|
||||
t instanceof ArrayType
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,3 +104,34 @@ predicate defaultAdditionalTaintStep(DataFlow::Node src, DataFlow::Node sink) {
|
||||
* but not in local taint.
|
||||
*/
|
||||
predicate defaultTaintBarrier(DataFlow::Node node) { none() }
|
||||
|
||||
/**
|
||||
* Holds if taint can flow from `instrIn` to `instrOut` through a call to a
|
||||
* modeled function.
|
||||
*/
|
||||
predicate modeledInstructionTaintStep(Instruction instrIn, Instruction instrOut) {
|
||||
exists(CallInstruction call, TaintFunction func, FunctionInput modelIn, FunctionOutput modelOut |
|
||||
instrIn = callInput(call, modelIn) and
|
||||
instrOut = callOutput(call, modelOut) and
|
||||
call.getStaticCallTarget() = func and
|
||||
func.hasTaintFlow(modelIn, modelOut)
|
||||
)
|
||||
or
|
||||
// Taint flow from one argument to another and data flow from an argument to a
|
||||
// return value. This happens in functions like `strcat` and `memcpy`. We
|
||||
// could model this flow in two separate steps, but that would add reverse
|
||||
// flow from the write side-effect to the call instruction, which may not be
|
||||
// desirable.
|
||||
exists(
|
||||
CallInstruction call, Function func, FunctionInput modelIn, OutParameterDeref modelMidOut,
|
||||
int indexMid, InParameter modelMidIn, OutReturnValue modelOut
|
||||
|
|
||||
instrIn = callInput(call, modelIn) and
|
||||
instrOut = callOutput(call, modelOut) and
|
||||
call.getStaticCallTarget() = func and
|
||||
func.(TaintFunction).hasTaintFlow(modelIn, modelMidOut) and
|
||||
func.(DataFlowFunction).hasDataFlow(modelMidIn, modelOut) and
|
||||
modelMidOut.isParameterDeref(indexMid) and
|
||||
modelMidIn.isParameter(indexMid)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,9 @@ private newtype TIRType =
|
||||
TIRBooleanType(int byteSize) { Language::hasBooleanType(byteSize) } or
|
||||
TIRSignedIntegerType(int byteSize) { Language::hasSignedIntegerType(byteSize) } or
|
||||
TIRUnsignedIntegerType(int byteSize) { Language::hasUnsignedIntegerType(byteSize) } or
|
||||
TIRFloatingPointType(int byteSize) { Language::hasFloatingPointType(byteSize) } or
|
||||
TIRFloatingPointType(int byteSize, int base, Language::TypeDomain domain) {
|
||||
Language::hasFloatingPointType(byteSize, base, domain)
|
||||
} or
|
||||
TIRAddressType(int byteSize) { Language::hasAddressType(byteSize) } or
|
||||
TIRFunctionAddressType(int byteSize) { Language::hasFunctionAddressType(byteSize) } or
|
||||
TIROpaqueType(Language::OpaqueTypeTag tag, int byteSize) {
|
||||
@@ -104,7 +106,7 @@ private class IRSizedType extends IRType {
|
||||
this = TIRBooleanType(byteSize) or
|
||||
this = TIRSignedIntegerType(byteSize) or
|
||||
this = TIRUnsignedIntegerType(byteSize) or
|
||||
this = TIRFloatingPointType(byteSize) or
|
||||
this = TIRFloatingPointType(byteSize, _, _) or
|
||||
this = TIRAddressType(byteSize) or
|
||||
this = TIRFunctionAddressType(byteSize) or
|
||||
this = TIROpaqueType(_, byteSize)
|
||||
@@ -133,7 +135,7 @@ class IRNumericType extends IRSizedType {
|
||||
IRNumericType() {
|
||||
this = TIRSignedIntegerType(byteSize) or
|
||||
this = TIRUnsignedIntegerType(byteSize) or
|
||||
this = TIRFloatingPointType(byteSize)
|
||||
this = TIRFloatingPointType(byteSize, _, _)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,14 +173,43 @@ class IRUnsignedIntegerType extends IRNumericType, TIRUnsignedIntegerType {
|
||||
* A floating-point type.
|
||||
*/
|
||||
class IRFloatingPointType extends IRNumericType, TIRFloatingPointType {
|
||||
final override string toString() { result = "float" + byteSize.toString() }
|
||||
final private int base;
|
||||
final private Language::TypeDomain domain;
|
||||
|
||||
IRFloatingPointType() { this = TIRFloatingPointType(_, base, domain) }
|
||||
|
||||
final override string toString() {
|
||||
result = getDomainPrefix() + getBaseString() + byteSize.toString()
|
||||
}
|
||||
|
||||
final override Language::LanguageType getCanonicalLanguageType() {
|
||||
result = Language::getCanonicalFloatingPointType(byteSize)
|
||||
result = Language::getCanonicalFloatingPointType(byteSize, base, domain)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
final override int getByteSize() { result = byteSize }
|
||||
|
||||
/** Gets the numeric base of the type. Can be either 2 (binary) or 10 (decimal). */
|
||||
final int getBase() { result = base }
|
||||
|
||||
/**
|
||||
* Gets the type domain of the type. Can be `RealDomain`, `ComplexDomain`, or `ImaginaryDomain`.
|
||||
*/
|
||||
final Language::TypeDomain getDomain() { result = domain }
|
||||
|
||||
private string getBaseString() {
|
||||
base = 2 and result = "float"
|
||||
or
|
||||
base = 10 and result = "decimal"
|
||||
}
|
||||
|
||||
private string getDomainPrefix() {
|
||||
domain instanceof Language::RealDomain and result = ""
|
||||
or
|
||||
domain instanceof Language::ComplexDomain and result = "c"
|
||||
or
|
||||
domain instanceof Language::ImaginaryDomain and result = "i"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -101,23 +101,24 @@ class IRBlock extends IRBlockBase {
|
||||
|
||||
private predicate startsBasicBlock(Instruction instr) {
|
||||
not instr instanceof PhiInstruction and
|
||||
(
|
||||
count(Instruction predecessor | instr = predecessor.getASuccessor()) != 1 // Multiple predecessors or no predecessor
|
||||
or
|
||||
exists(Instruction predecessor |
|
||||
instr = predecessor.getASuccessor() and
|
||||
strictcount(Instruction other | other = predecessor.getASuccessor()) > 1
|
||||
) // Predecessor has multiple successors
|
||||
or
|
||||
exists(Instruction predecessor, EdgeKind kind |
|
||||
instr = predecessor.getSuccessor(kind) and
|
||||
not kind instanceof GotoEdge
|
||||
) // Incoming edge is not a GotoEdge
|
||||
or
|
||||
exists(Instruction predecessor |
|
||||
instr = Construction::getInstructionBackEdgeSuccessor(predecessor, _)
|
||||
) // A back edge enters this instruction
|
||||
)
|
||||
not adjacentInBlock(_, instr)
|
||||
}
|
||||
|
||||
/** Holds if `i2` follows `i1` in a `IRBlock`. */
|
||||
private predicate adjacentInBlock(Instruction i1, Instruction i2) {
|
||||
// - i2 must be the only successor of i1
|
||||
i2 = unique(Instruction i | i = i1.getASuccessor()) and
|
||||
// - i1 must be the only predecessor of i2
|
||||
i1 = unique(Instruction i | i.getASuccessor() = i2) and
|
||||
// - The edge between the two must be a GotoEdge. We just check that one
|
||||
// exists since we've already checked that it's unique.
|
||||
exists(GotoEdge edgeKind | exists(i1.getSuccessor(edgeKind))) and
|
||||
// - The edge must not be a back edge. This means we get the same back edges
|
||||
// in the basic-block graph as we do in the raw CFG.
|
||||
not exists(Construction::getInstructionBackEdgeSuccessor(i1, _))
|
||||
// This predicate could be simplified to remove one of the `unique`s if we
|
||||
// were willing to rely on the CFG being well-formed and thus never having
|
||||
// more than one successor to an instruction that has a `GotoEdge` out of it.
|
||||
}
|
||||
|
||||
private predicate isEntryBlock(TIRBlock block) {
|
||||
@@ -129,12 +130,6 @@ private module Cached {
|
||||
cached
|
||||
newtype TIRBlock = MkIRBlock(Instruction firstInstr) { startsBasicBlock(firstInstr) }
|
||||
|
||||
/** Holds if `i2` follows `i1` in a `IRBlock`. */
|
||||
private predicate adjacentInBlock(Instruction i1, Instruction i2) {
|
||||
exists(GotoEdge edgeKind | i2 = i1.getSuccessor(edgeKind)) and
|
||||
not startsBasicBlock(i2)
|
||||
}
|
||||
|
||||
/** Holds if `i` is the `index`th instruction the block starting with `first`. */
|
||||
private Instruction getInstructionFromFirst(Instruction first, int index) =
|
||||
shortestDistances(startsBasicBlock/1, adjacentInBlock/2)(first, result, index)
|
||||
|
||||
@@ -149,6 +149,26 @@ module InstructionSanity {
|
||||
count(instr.getBlock().getAPredecessor()) < 2
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a memory operand is connected to a definition with an unmodeled result, other than
|
||||
* `UnmodeledDefinition` itself.
|
||||
*/
|
||||
query predicate memoryOperandDefinitionIsUnmodeled(
|
||||
Instruction instr, string message, IRFunction func, string funcText
|
||||
) {
|
||||
exists(MemoryOperand operand, Instruction def |
|
||||
operand = instr.getAnOperand() and
|
||||
not operand instanceof UnmodeledUseOperand and
|
||||
def = operand.getAnyDef() and
|
||||
not def.isResultModeled() and
|
||||
not def instanceof UnmodeledDefinitionInstruction and
|
||||
message =
|
||||
"Memory operand definition has unmodeled result, but is not the `UnmodeledDefinition` instruction in function '$@'" and
|
||||
func = instr.getEnclosingIRFunction() and
|
||||
funcText = Language::getIdentityString(func.getFunction())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if operand `operand` consumes a value that was defined in
|
||||
* a different function.
|
||||
|
||||
@@ -190,14 +190,15 @@ class Instruction extends Construction::TInstruction {
|
||||
final Language::Location getLocation() { result = getAST().getLocation() }
|
||||
|
||||
/**
|
||||
* Gets the `Expr` whose result is computed by this instruction, if any.
|
||||
* Gets the `Expr` whose result is computed by this instruction, if any. The `Expr` may be a
|
||||
* conversion.
|
||||
*/
|
||||
final Language::Expr getConvertedResultExpression() {
|
||||
result = Construction::getInstructionConvertedResultExpression(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the unconverted `Expr` whose result is computed by this instruction, if any.
|
||||
* Gets the unconverted form of the `Expr` whose result is computed by this instruction, if any.
|
||||
*/
|
||||
final Language::Expr getUnconvertedResultExpression() {
|
||||
result = Construction::getInstructionUnconvertedResultExpression(this)
|
||||
@@ -525,7 +526,7 @@ class ReturnValueInstruction extends ReturnInstruction {
|
||||
final Instruction getReturnValue() { result = getReturnValueOperand().getDef() }
|
||||
}
|
||||
|
||||
class ReturnIndirectionInstruction extends Instruction {
|
||||
class ReturnIndirectionInstruction extends VariableInstruction {
|
||||
ReturnIndirectionInstruction() { getOpcode() instanceof Opcode::ReturnIndirection }
|
||||
|
||||
final SideEffectOperand getSideEffectOperand() { result = getAnOperand() }
|
||||
@@ -535,6 +536,12 @@ class ReturnIndirectionInstruction extends Instruction {
|
||||
final AddressOperand getSourceAddressOperand() { result = getAnOperand() }
|
||||
|
||||
final Instruction getSourceAddress() { result = getSourceAddressOperand().getDef() }
|
||||
|
||||
/**
|
||||
* Gets the parameter for which this instruction reads the final pointed-to value within the
|
||||
* function.
|
||||
*/
|
||||
final Language::Parameter getParameter() { result = var.(IRUserVariable).getVariable() }
|
||||
}
|
||||
|
||||
class CopyInstruction extends Instruction {
|
||||
|
||||
@@ -14,8 +14,7 @@ int getConstantValue(Instruction instr) {
|
||||
or
|
||||
exists(PhiInstruction phi |
|
||||
phi = instr and
|
||||
result = max(Operand op | op = phi.getAnInputOperand() | getConstantValue(op.getDef())) and
|
||||
result = min(Operand op | op = phi.getAnInputOperand() | getConstantValue(op.getDef()))
|
||||
result = unique(Operand op | op = phi.getAnInputOperand() | getConstantValue(op.getDef()))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,11 +6,12 @@ private import DebugSSA
|
||||
|
||||
bindingset[offset]
|
||||
private string getKeySuffixForOffset(int offset) {
|
||||
offset >= 0 and
|
||||
if offset % 2 = 0 then result = "" else result = "_Chi"
|
||||
}
|
||||
|
||||
bindingset[offset]
|
||||
private int getIndexForOffset(int offset) { result = offset / 2 }
|
||||
private int getIndexForOffset(int offset) { offset >= 0 and result = offset / 2 }
|
||||
|
||||
/**
|
||||
* Property provide that dumps the memory access of each result. Useful for debugging SSA
|
||||
|
||||
@@ -101,23 +101,24 @@ class IRBlock extends IRBlockBase {
|
||||
|
||||
private predicate startsBasicBlock(Instruction instr) {
|
||||
not instr instanceof PhiInstruction and
|
||||
(
|
||||
count(Instruction predecessor | instr = predecessor.getASuccessor()) != 1 // Multiple predecessors or no predecessor
|
||||
or
|
||||
exists(Instruction predecessor |
|
||||
instr = predecessor.getASuccessor() and
|
||||
strictcount(Instruction other | other = predecessor.getASuccessor()) > 1
|
||||
) // Predecessor has multiple successors
|
||||
or
|
||||
exists(Instruction predecessor, EdgeKind kind |
|
||||
instr = predecessor.getSuccessor(kind) and
|
||||
not kind instanceof GotoEdge
|
||||
) // Incoming edge is not a GotoEdge
|
||||
or
|
||||
exists(Instruction predecessor |
|
||||
instr = Construction::getInstructionBackEdgeSuccessor(predecessor, _)
|
||||
) // A back edge enters this instruction
|
||||
)
|
||||
not adjacentInBlock(_, instr)
|
||||
}
|
||||
|
||||
/** Holds if `i2` follows `i1` in a `IRBlock`. */
|
||||
private predicate adjacentInBlock(Instruction i1, Instruction i2) {
|
||||
// - i2 must be the only successor of i1
|
||||
i2 = unique(Instruction i | i = i1.getASuccessor()) and
|
||||
// - i1 must be the only predecessor of i2
|
||||
i1 = unique(Instruction i | i.getASuccessor() = i2) and
|
||||
// - The edge between the two must be a GotoEdge. We just check that one
|
||||
// exists since we've already checked that it's unique.
|
||||
exists(GotoEdge edgeKind | exists(i1.getSuccessor(edgeKind))) and
|
||||
// - The edge must not be a back edge. This means we get the same back edges
|
||||
// in the basic-block graph as we do in the raw CFG.
|
||||
not exists(Construction::getInstructionBackEdgeSuccessor(i1, _))
|
||||
// This predicate could be simplified to remove one of the `unique`s if we
|
||||
// were willing to rely on the CFG being well-formed and thus never having
|
||||
// more than one successor to an instruction that has a `GotoEdge` out of it.
|
||||
}
|
||||
|
||||
private predicate isEntryBlock(TIRBlock block) {
|
||||
@@ -129,12 +130,6 @@ private module Cached {
|
||||
cached
|
||||
newtype TIRBlock = MkIRBlock(Instruction firstInstr) { startsBasicBlock(firstInstr) }
|
||||
|
||||
/** Holds if `i2` follows `i1` in a `IRBlock`. */
|
||||
private predicate adjacentInBlock(Instruction i1, Instruction i2) {
|
||||
exists(GotoEdge edgeKind | i2 = i1.getSuccessor(edgeKind)) and
|
||||
not startsBasicBlock(i2)
|
||||
}
|
||||
|
||||
/** Holds if `i` is the `index`th instruction the block starting with `first`. */
|
||||
private Instruction getInstructionFromFirst(Instruction first, int index) =
|
||||
shortestDistances(startsBasicBlock/1, adjacentInBlock/2)(first, result, index)
|
||||
|
||||
@@ -149,6 +149,26 @@ module InstructionSanity {
|
||||
count(instr.getBlock().getAPredecessor()) < 2
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a memory operand is connected to a definition with an unmodeled result, other than
|
||||
* `UnmodeledDefinition` itself.
|
||||
*/
|
||||
query predicate memoryOperandDefinitionIsUnmodeled(
|
||||
Instruction instr, string message, IRFunction func, string funcText
|
||||
) {
|
||||
exists(MemoryOperand operand, Instruction def |
|
||||
operand = instr.getAnOperand() and
|
||||
not operand instanceof UnmodeledUseOperand and
|
||||
def = operand.getAnyDef() and
|
||||
not def.isResultModeled() and
|
||||
not def instanceof UnmodeledDefinitionInstruction and
|
||||
message =
|
||||
"Memory operand definition has unmodeled result, but is not the `UnmodeledDefinition` instruction in function '$@'" and
|
||||
func = instr.getEnclosingIRFunction() and
|
||||
funcText = Language::getIdentityString(func.getFunction())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if operand `operand` consumes a value that was defined in
|
||||
* a different function.
|
||||
|
||||
@@ -190,14 +190,15 @@ class Instruction extends Construction::TInstruction {
|
||||
final Language::Location getLocation() { result = getAST().getLocation() }
|
||||
|
||||
/**
|
||||
* Gets the `Expr` whose result is computed by this instruction, if any.
|
||||
* Gets the `Expr` whose result is computed by this instruction, if any. The `Expr` may be a
|
||||
* conversion.
|
||||
*/
|
||||
final Language::Expr getConvertedResultExpression() {
|
||||
result = Construction::getInstructionConvertedResultExpression(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the unconverted `Expr` whose result is computed by this instruction, if any.
|
||||
* Gets the unconverted form of the `Expr` whose result is computed by this instruction, if any.
|
||||
*/
|
||||
final Language::Expr getUnconvertedResultExpression() {
|
||||
result = Construction::getInstructionUnconvertedResultExpression(this)
|
||||
@@ -525,7 +526,7 @@ class ReturnValueInstruction extends ReturnInstruction {
|
||||
final Instruction getReturnValue() { result = getReturnValueOperand().getDef() }
|
||||
}
|
||||
|
||||
class ReturnIndirectionInstruction extends Instruction {
|
||||
class ReturnIndirectionInstruction extends VariableInstruction {
|
||||
ReturnIndirectionInstruction() { getOpcode() instanceof Opcode::ReturnIndirection }
|
||||
|
||||
final SideEffectOperand getSideEffectOperand() { result = getAnOperand() }
|
||||
@@ -535,6 +536,12 @@ class ReturnIndirectionInstruction extends Instruction {
|
||||
final AddressOperand getSourceAddressOperand() { result = getAnOperand() }
|
||||
|
||||
final Instruction getSourceAddress() { result = getSourceAddressOperand().getDef() }
|
||||
|
||||
/**
|
||||
* Gets the parameter for which this instruction reads the final pointed-to value within the
|
||||
* function.
|
||||
*/
|
||||
final Language::Parameter getParameter() { result = var.(IRUserVariable).getVariable() }
|
||||
}
|
||||
|
||||
class CopyInstruction extends Instruction {
|
||||
|
||||
@@ -14,8 +14,7 @@ int getConstantValue(Instruction instr) {
|
||||
or
|
||||
exists(PhiInstruction phi |
|
||||
phi = instr and
|
||||
result = max(Operand op | op = phi.getAnInputOperand() | getConstantValue(op.getDef())) and
|
||||
result = min(Operand op | op = phi.getAnInputOperand() | getConstantValue(op.getDef()))
|
||||
result = unique(Operand op | op = phi.getAnInputOperand() | getConstantValue(op.getDef()))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -324,6 +324,16 @@ class TranslatedFunctionCall extends TranslatedCallExpr, TranslatedDirectCall {
|
||||
override predicate hasWriteSideEffect() {
|
||||
not expr.getTarget().(SideEffectFunction).hasOnlySpecificWriteSideEffects()
|
||||
}
|
||||
|
||||
override Instruction getQualifierResult() {
|
||||
hasQualifier() and
|
||||
result = getQualifier().getResult()
|
||||
}
|
||||
|
||||
override predicate hasQualifier() {
|
||||
exists(getQualifier()) and
|
||||
not exists(MemberFunction func | expr.getTarget() = func and func.isStatic())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -200,7 +200,11 @@ private predicate usedAsCondition(Expr expr) {
|
||||
or
|
||||
exists(IfStmt ifStmt | ifStmt.getCondition().getFullyConverted() = expr)
|
||||
or
|
||||
exists(ConditionalExpr condExpr | condExpr.getCondition().getFullyConverted() = expr)
|
||||
exists(ConditionalExpr condExpr |
|
||||
// The two-operand form of `ConditionalExpr` treats its condition as a value, since it needs to
|
||||
// be reused as a value if the condition is true.
|
||||
condExpr.getCondition().getFullyConverted() = expr and not condExpr.isTwoOperand()
|
||||
)
|
||||
or
|
||||
exists(NotExpr notExpr |
|
||||
notExpr.getOperand().getFullyConverted() = expr and
|
||||
@@ -463,7 +467,9 @@ newtype TTranslatedElement =
|
||||
expr = call.getArgument(n).getFullyConverted()
|
||||
or
|
||||
expr = call.getQualifier().getFullyConverted() and
|
||||
n = -1
|
||||
n = -1 and
|
||||
// Exclude calls to static member functions. They don't modify the qualifier
|
||||
not exists(MemberFunction func | func = call.getTarget() and func.isStatic())
|
||||
) and
|
||||
(
|
||||
call.getTarget().(SideEffectFunction).hasSpecificReadSideEffect(n, _) and
|
||||
|
||||
@@ -1100,13 +1100,36 @@ private Opcode binaryBitwiseOpcode(BinaryBitwiseOperation expr) {
|
||||
}
|
||||
|
||||
private Opcode binaryArithmeticOpcode(BinaryArithmeticOperation expr) {
|
||||
expr instanceof AddExpr and result instanceof Opcode::Add
|
||||
(
|
||||
expr instanceof AddExpr
|
||||
or
|
||||
expr instanceof ImaginaryRealAddExpr
|
||||
or
|
||||
expr instanceof RealImaginaryAddExpr
|
||||
) and
|
||||
result instanceof Opcode::Add
|
||||
or
|
||||
expr instanceof SubExpr and result instanceof Opcode::Sub
|
||||
(
|
||||
expr instanceof SubExpr
|
||||
or
|
||||
expr instanceof ImaginaryRealSubExpr
|
||||
or
|
||||
expr instanceof RealImaginarySubExpr
|
||||
) and
|
||||
result instanceof Opcode::Sub
|
||||
or
|
||||
expr instanceof MulExpr and result instanceof Opcode::Mul
|
||||
(
|
||||
expr instanceof MulExpr
|
||||
or
|
||||
expr instanceof ImaginaryMulExpr
|
||||
) and
|
||||
result instanceof Opcode::Mul
|
||||
or
|
||||
expr instanceof DivExpr and result instanceof Opcode::Div
|
||||
(
|
||||
expr instanceof DivExpr or
|
||||
expr instanceof ImaginaryDivExpr
|
||||
) and
|
||||
result instanceof Opcode::Div
|
||||
or
|
||||
expr instanceof RemExpr and result instanceof Opcode::Rem
|
||||
or
|
||||
@@ -1735,20 +1758,20 @@ class TranslatedDestructorFieldDestruction extends TranslatedNonConstantExpr, St
|
||||
private TranslatedExpr getDestructorCall() { result = getTranslatedExpr(expr.getExpr()) }
|
||||
}
|
||||
|
||||
class TranslatedConditionalExpr extends TranslatedNonConstantExpr, ConditionContext {
|
||||
/**
|
||||
* The IR translation of the `?:` operator. This class has the portions of the implementation that
|
||||
* are shared between the standard three-operand form (`a ? b : c`) and the GCC-extension
|
||||
* two-operand form (`a ?: c`).
|
||||
*/
|
||||
abstract class TranslatedConditionalExpr extends TranslatedNonConstantExpr {
|
||||
override ConditionalExpr expr;
|
||||
|
||||
final override TranslatedElement getChild(int id) {
|
||||
id = 0 and result = getCondition()
|
||||
or
|
||||
id = 1 and result = getThen()
|
||||
or
|
||||
id = 2 and result = getElse()
|
||||
}
|
||||
|
||||
override Instruction getFirstInstruction() { result = getCondition().getFirstInstruction() }
|
||||
|
||||
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
|
||||
// Note that the ternary flavor needs no explicit `ConditionalBranch` instruction here, because
|
||||
// the condition is a `TranslatedCondition`, which will simply connect the successor edges of
|
||||
// the condition directly to the appropriate then/else block via
|
||||
// `getChild[True|False]Successor()`.
|
||||
// The binary flavor will override this predicate to add the `ConditionalBranch`.
|
||||
not resultIsVoid() and
|
||||
(
|
||||
(
|
||||
@@ -1843,13 +1866,13 @@ class TranslatedConditionalExpr extends TranslatedNonConstantExpr, ConditionCont
|
||||
)
|
||||
}
|
||||
|
||||
override predicate hasTempVariable(TempVariableTag tag, CppType type) {
|
||||
final override predicate hasTempVariable(TempVariableTag tag, CppType type) {
|
||||
not resultIsVoid() and
|
||||
tag = ConditionValueTempVar() and
|
||||
type = getResultType()
|
||||
}
|
||||
|
||||
override IRVariable getInstructionVariable(InstructionTag tag) {
|
||||
final override IRVariable getInstructionVariable(InstructionTag tag) {
|
||||
not resultIsVoid() and
|
||||
(
|
||||
tag = ConditionValueTrueTempAddressTag() or
|
||||
@@ -1859,25 +1882,75 @@ class TranslatedConditionalExpr extends TranslatedNonConstantExpr, ConditionCont
|
||||
result = getTempVariable(ConditionValueTempVar())
|
||||
}
|
||||
|
||||
override Instruction getResult() {
|
||||
final override Instruction getResult() {
|
||||
not resultIsVoid() and
|
||||
result = getInstruction(ConditionValueResultLoadTag())
|
||||
}
|
||||
|
||||
override Instruction getChildSuccessor(TranslatedElement child) {
|
||||
child = getElse() and
|
||||
if elseIsVoid()
|
||||
then result = getParent().getChildSuccessor(this)
|
||||
else result = getInstruction(ConditionValueFalseTempAddressTag())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `TranslatedExpr` for the "then" result. Note that nothing in the base implementation
|
||||
* of this class assumes that `getThen()` is disjoint from `getCondition()`.
|
||||
*/
|
||||
abstract TranslatedExpr getThen();
|
||||
|
||||
/**
|
||||
* Gets the `TranslatedExpr` for the "else" result.
|
||||
*/
|
||||
final TranslatedExpr getElse() { result = getTranslatedExpr(expr.getElse().getFullyConverted()) }
|
||||
|
||||
final predicate thenIsVoid() {
|
||||
getThen().getResultType().getIRType() instanceof IRVoidType
|
||||
or
|
||||
// A `ThrowExpr.getType()` incorrectly returns the type of exception being
|
||||
// thrown, rather than `void`. Handle that case here.
|
||||
expr.getThen() instanceof ThrowExpr
|
||||
}
|
||||
|
||||
private predicate elseIsVoid() {
|
||||
getElse().getResultType().getIRType() instanceof IRVoidType
|
||||
or
|
||||
// A `ThrowExpr.getType()` incorrectly returns the type of exception being
|
||||
// thrown, rather than `void`. Handle that case here.
|
||||
expr.getElse() instanceof ThrowExpr
|
||||
}
|
||||
|
||||
private predicate resultIsVoid() { getResultType().getIRType() instanceof IRVoidType }
|
||||
}
|
||||
|
||||
/**
|
||||
* The IR translation of the ternary conditional operator (`a ? b : c`).
|
||||
* For this version, we expand the condition as a `TranslatedCondition`, rather than a
|
||||
* `TranslatedExpr`, to simplify the control flow in the presence of short-ciruit logical operators.
|
||||
*/
|
||||
class TranslatedTernaryConditionalExpr extends TranslatedConditionalExpr, ConditionContext {
|
||||
TranslatedTernaryConditionalExpr() { not expr.isTwoOperand() }
|
||||
|
||||
final override TranslatedElement getChild(int id) {
|
||||
id = 0 and result = getCondition()
|
||||
or
|
||||
id = 1 and result = getThen()
|
||||
or
|
||||
id = 2 and result = getElse()
|
||||
}
|
||||
|
||||
override Instruction getFirstInstruction() { result = getCondition().getFirstInstruction() }
|
||||
|
||||
override Instruction getChildSuccessor(TranslatedElement child) {
|
||||
result = TranslatedConditionalExpr.super.getChildSuccessor(child)
|
||||
or
|
||||
(
|
||||
child = getThen() and
|
||||
if thenIsVoid()
|
||||
then result = getParent().getChildSuccessor(this)
|
||||
else result = getInstruction(ConditionValueTrueTempAddressTag())
|
||||
)
|
||||
or
|
||||
(
|
||||
child = getElse() and
|
||||
if elseIsVoid()
|
||||
then result = getParent().getChildSuccessor(this)
|
||||
else result = getInstruction(ConditionValueFalseTempAddressTag())
|
||||
)
|
||||
}
|
||||
|
||||
override Instruction getChildTrueSuccessor(TranslatedCondition child) {
|
||||
@@ -1894,31 +1967,81 @@ class TranslatedConditionalExpr extends TranslatedNonConstantExpr, ConditionCont
|
||||
result = getTranslatedCondition(expr.getCondition().getFullyConverted())
|
||||
}
|
||||
|
||||
private TranslatedExpr getThen() {
|
||||
final override TranslatedExpr getThen() {
|
||||
result = getTranslatedExpr(expr.getThen().getFullyConverted())
|
||||
}
|
||||
}
|
||||
|
||||
private TranslatedExpr getElse() {
|
||||
result = getTranslatedExpr(expr.getElse().getFullyConverted())
|
||||
}
|
||||
/**
|
||||
* The IR translation of a two-operand conditional operator (`a ?: b`). This is a GCC language
|
||||
* extension.
|
||||
* This version of the conditional expression returns its first operand (the condition) if that
|
||||
* condition is non-zero. Since we'll be reusing the value of the condition, we'll compute that
|
||||
* value directly before branching, even if that value was a short-circuit logical expression.
|
||||
*/
|
||||
class TranslatedBinaryConditionalExpr extends TranslatedConditionalExpr {
|
||||
TranslatedBinaryConditionalExpr() { expr.isTwoOperand() }
|
||||
|
||||
private predicate thenIsVoid() {
|
||||
getThen().getResultType().getIRType() instanceof IRVoidType
|
||||
final override TranslatedElement getChild(int id) {
|
||||
// We only truly have two children, because our "condition" and "then" are the same as far as
|
||||
// the extractor is concerned.
|
||||
id = 0 and result = getCondition()
|
||||
or
|
||||
// A `ThrowExpr.getType()` incorrectly returns the type of exception being
|
||||
// thrown, rather than `void`. Handle that case here.
|
||||
expr.getThen() instanceof ThrowExpr
|
||||
id = 1 and result = getElse()
|
||||
}
|
||||
|
||||
private predicate elseIsVoid() {
|
||||
getElse().getResultType().getIRType() instanceof IRVoidType
|
||||
override Instruction getFirstInstruction() { result = getCondition().getFirstInstruction() }
|
||||
|
||||
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
|
||||
super.hasInstruction(opcode, tag, resultType)
|
||||
or
|
||||
// A `ThrowExpr.getType()` incorrectly returns the type of exception being
|
||||
// thrown, rather than `void`. Handle that case here.
|
||||
expr.getElse() instanceof ThrowExpr
|
||||
// For the binary variant, we create our own conditional branch.
|
||||
tag = ValueConditionConditionalBranchTag() and
|
||||
opcode instanceof Opcode::ConditionalBranch and
|
||||
resultType = getVoidType()
|
||||
}
|
||||
|
||||
private predicate resultIsVoid() { getResultType().getIRType() instanceof IRVoidType }
|
||||
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
|
||||
result = super.getInstructionSuccessor(tag, kind)
|
||||
or
|
||||
tag = ValueConditionConditionalBranchTag() and
|
||||
(
|
||||
kind instanceof TrueEdge and
|
||||
result = getInstruction(ConditionValueTrueTempAddressTag())
|
||||
or
|
||||
kind instanceof FalseEdge and
|
||||
result = getElse().getFirstInstruction()
|
||||
)
|
||||
}
|
||||
|
||||
override Instruction getInstructionOperand(InstructionTag tag, OperandTag operandTag) {
|
||||
result = super.getInstructionOperand(tag, operandTag)
|
||||
or
|
||||
tag = ValueConditionConditionalBranchTag() and
|
||||
operandTag instanceof ConditionOperandTag and
|
||||
result = getCondition().getResult()
|
||||
}
|
||||
|
||||
override Instruction getChildSuccessor(TranslatedElement child) {
|
||||
result = super.getChildSuccessor(child)
|
||||
or
|
||||
child = getCondition() and result = getInstruction(ValueConditionConditionalBranchTag())
|
||||
}
|
||||
|
||||
private TranslatedExpr getCondition() {
|
||||
result = getTranslatedExpr(expr.getCondition().getFullyConverted())
|
||||
}
|
||||
|
||||
final override TranslatedExpr getThen() {
|
||||
// The extractor returns the exact same expression for `ConditionalExpr::getCondition()` and
|
||||
// `ConditionalExpr::getThen()`, even though the condition may have been converted to `bool`,
|
||||
// and the "then" may have been converted to the result type. We'll strip the top-level implicit
|
||||
// conversions from this, to skip any conversion to `bool`. We don't have enough information to
|
||||
// know how to convert the result to the destination type, especially in the class pointer case,
|
||||
// so we'll still sometimes wind up with one operand as the wrong type. This is better than
|
||||
// always converting the "then" operand to `bool`, which is almost always the wrong type.
|
||||
result = getTranslatedExpr(expr.getThen().getExplicitlyConverted())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,6 +49,11 @@ CppType getEllipsisVariablePRValueType() {
|
||||
|
||||
CppType getEllipsisVariableGLValueType() { result = getTypeForGLValue(any(UnknownType t)) }
|
||||
|
||||
/**
|
||||
* Holds if the function returns a value, as opposed to returning `void`.
|
||||
*/
|
||||
predicate hasReturnValue(Function func) { not func.getUnspecifiedType() instanceof VoidType }
|
||||
|
||||
/**
|
||||
* Represents the IR translation of a function. This is the root elements for
|
||||
* all other elements associated with this function.
|
||||
@@ -312,7 +317,7 @@ class TranslatedFunction extends TranslatedElement, TTranslatedFunction {
|
||||
/**
|
||||
* Holds if the function has a non-`void` return type.
|
||||
*/
|
||||
final predicate hasReturnValue() { not func.getUnspecifiedType() instanceof VoidType }
|
||||
final predicate hasReturnValue() { hasReturnValue(func) }
|
||||
|
||||
/**
|
||||
* Gets the single `UnmodeledDefinition` instruction for this function.
|
||||
@@ -454,7 +459,7 @@ abstract class TranslatedParameter extends TranslatedElement {
|
||||
result = getInstruction(InitializerVariableAddressTag())
|
||||
or
|
||||
operandTag instanceof LoadOperandTag and
|
||||
result = getInstruction(InitializerStoreTag())
|
||||
result = getTranslatedFunction(getFunction()).getUnmodeledDefinitionInstruction()
|
||||
)
|
||||
or
|
||||
tag = InitializerIndirectStoreTag() and
|
||||
@@ -744,4 +749,9 @@ class TranslatedReadEffect extends TranslatedElement, TTranslatedReadEffect {
|
||||
operandTag = sideEffectOperand() and
|
||||
result = getUnknownType()
|
||||
}
|
||||
|
||||
final override IRVariable getInstructionVariable(InstructionTag tag) {
|
||||
tag = OnlyInstructionTag() and
|
||||
result = getIRUserVariable(getFunction(), param)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,8 +131,11 @@ abstract class TranslatedReturnStmt extends TranslatedStmt {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The IR translation of a `return` statement that returns a value.
|
||||
*/
|
||||
class TranslatedReturnValueStmt extends TranslatedReturnStmt, TranslatedVariableInitialization {
|
||||
TranslatedReturnValueStmt() { stmt.hasExpr() }
|
||||
TranslatedReturnValueStmt() { stmt.hasExpr() and hasReturnValue(stmt.getEnclosingFunction()) }
|
||||
|
||||
final override Instruction getInitializationSuccessor() {
|
||||
result = getEnclosingFunction().getReturnSuccessorInstruction()
|
||||
@@ -147,8 +150,49 @@ class TranslatedReturnValueStmt extends TranslatedReturnStmt, TranslatedVariable
|
||||
final override IRVariable getIRVariable() { result = getEnclosingFunction().getReturnVariable() }
|
||||
}
|
||||
|
||||
/**
|
||||
* The IR translation of a `return` statement that returns an expression of `void` type.
|
||||
*/
|
||||
class TranslatedReturnVoidExpressionStmt extends TranslatedReturnStmt {
|
||||
TranslatedReturnVoidExpressionStmt() {
|
||||
stmt.hasExpr() and not hasReturnValue(stmt.getEnclosingFunction())
|
||||
}
|
||||
|
||||
override TranslatedElement getChild(int id) {
|
||||
id = 0 and
|
||||
result = getExpr()
|
||||
}
|
||||
|
||||
override Instruction getFirstInstruction() { result = getExpr().getFirstInstruction() }
|
||||
|
||||
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
|
||||
tag = OnlyInstructionTag() and
|
||||
opcode instanceof Opcode::NoOp and
|
||||
resultType = getVoidType()
|
||||
}
|
||||
|
||||
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
|
||||
tag = OnlyInstructionTag() and
|
||||
result = getEnclosingFunction().getReturnSuccessorInstruction() and
|
||||
kind instanceof GotoEdge
|
||||
}
|
||||
|
||||
override Instruction getChildSuccessor(TranslatedElement child) {
|
||||
child = getExpr() and
|
||||
result = getInstruction(OnlyInstructionTag())
|
||||
}
|
||||
|
||||
private TranslatedExpr getExpr() { result = getTranslatedExpr(stmt.getExpr()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* The IR translation of a `return` statement that does not return a value. This includes implicit
|
||||
* return statements at the end of `void`-returning functions.
|
||||
*/
|
||||
class TranslatedReturnVoidStmt extends TranslatedReturnStmt {
|
||||
TranslatedReturnVoidStmt() { not stmt.hasExpr() }
|
||||
TranslatedReturnVoidStmt() {
|
||||
not stmt.hasExpr() and not hasReturnValue(stmt.getEnclosingFunction())
|
||||
}
|
||||
|
||||
override TranslatedElement getChild(int id) { none() }
|
||||
|
||||
@@ -169,6 +213,33 @@ class TranslatedReturnVoidStmt extends TranslatedReturnStmt {
|
||||
override Instruction getChildSuccessor(TranslatedElement child) { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* The IR translation of an implicit `return` statement generated by the extractor to handle control
|
||||
* flow that reaches the end of a non-`void`-returning function body. Since such control flow
|
||||
* produces undefined behavior, we simply generate an `Unreached` instruction to prevent that flow
|
||||
* from continuing on to pollute other analysis. The assumption is that the developer is certain
|
||||
* that the implicit `return` is unreachable, even if the compiler cannot prove it.
|
||||
*/
|
||||
class TranslatedUnreachableReturnStmt extends TranslatedReturnStmt {
|
||||
TranslatedUnreachableReturnStmt() {
|
||||
not stmt.hasExpr() and hasReturnValue(stmt.getEnclosingFunction())
|
||||
}
|
||||
|
||||
override TranslatedElement getChild(int id) { none() }
|
||||
|
||||
override Instruction getFirstInstruction() { result = getInstruction(OnlyInstructionTag()) }
|
||||
|
||||
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
|
||||
tag = OnlyInstructionTag() and
|
||||
opcode instanceof Opcode::Unreached and
|
||||
resultType = getVoidType()
|
||||
}
|
||||
|
||||
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) { none() }
|
||||
|
||||
override Instruction getChildSuccessor(TranslatedElement child) { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* The IR translation of a C++ `try` statement.
|
||||
*/
|
||||
|
||||
@@ -101,23 +101,24 @@ class IRBlock extends IRBlockBase {
|
||||
|
||||
private predicate startsBasicBlock(Instruction instr) {
|
||||
not instr instanceof PhiInstruction and
|
||||
(
|
||||
count(Instruction predecessor | instr = predecessor.getASuccessor()) != 1 // Multiple predecessors or no predecessor
|
||||
or
|
||||
exists(Instruction predecessor |
|
||||
instr = predecessor.getASuccessor() and
|
||||
strictcount(Instruction other | other = predecessor.getASuccessor()) > 1
|
||||
) // Predecessor has multiple successors
|
||||
or
|
||||
exists(Instruction predecessor, EdgeKind kind |
|
||||
instr = predecessor.getSuccessor(kind) and
|
||||
not kind instanceof GotoEdge
|
||||
) // Incoming edge is not a GotoEdge
|
||||
or
|
||||
exists(Instruction predecessor |
|
||||
instr = Construction::getInstructionBackEdgeSuccessor(predecessor, _)
|
||||
) // A back edge enters this instruction
|
||||
)
|
||||
not adjacentInBlock(_, instr)
|
||||
}
|
||||
|
||||
/** Holds if `i2` follows `i1` in a `IRBlock`. */
|
||||
private predicate adjacentInBlock(Instruction i1, Instruction i2) {
|
||||
// - i2 must be the only successor of i1
|
||||
i2 = unique(Instruction i | i = i1.getASuccessor()) and
|
||||
// - i1 must be the only predecessor of i2
|
||||
i1 = unique(Instruction i | i.getASuccessor() = i2) and
|
||||
// - The edge between the two must be a GotoEdge. We just check that one
|
||||
// exists since we've already checked that it's unique.
|
||||
exists(GotoEdge edgeKind | exists(i1.getSuccessor(edgeKind))) and
|
||||
// - The edge must not be a back edge. This means we get the same back edges
|
||||
// in the basic-block graph as we do in the raw CFG.
|
||||
not exists(Construction::getInstructionBackEdgeSuccessor(i1, _))
|
||||
// This predicate could be simplified to remove one of the `unique`s if we
|
||||
// were willing to rely on the CFG being well-formed and thus never having
|
||||
// more than one successor to an instruction that has a `GotoEdge` out of it.
|
||||
}
|
||||
|
||||
private predicate isEntryBlock(TIRBlock block) {
|
||||
@@ -129,12 +130,6 @@ private module Cached {
|
||||
cached
|
||||
newtype TIRBlock = MkIRBlock(Instruction firstInstr) { startsBasicBlock(firstInstr) }
|
||||
|
||||
/** Holds if `i2` follows `i1` in a `IRBlock`. */
|
||||
private predicate adjacentInBlock(Instruction i1, Instruction i2) {
|
||||
exists(GotoEdge edgeKind | i2 = i1.getSuccessor(edgeKind)) and
|
||||
not startsBasicBlock(i2)
|
||||
}
|
||||
|
||||
/** Holds if `i` is the `index`th instruction the block starting with `first`. */
|
||||
private Instruction getInstructionFromFirst(Instruction first, int index) =
|
||||
shortestDistances(startsBasicBlock/1, adjacentInBlock/2)(first, result, index)
|
||||
|
||||
@@ -149,6 +149,26 @@ module InstructionSanity {
|
||||
count(instr.getBlock().getAPredecessor()) < 2
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a memory operand is connected to a definition with an unmodeled result, other than
|
||||
* `UnmodeledDefinition` itself.
|
||||
*/
|
||||
query predicate memoryOperandDefinitionIsUnmodeled(
|
||||
Instruction instr, string message, IRFunction func, string funcText
|
||||
) {
|
||||
exists(MemoryOperand operand, Instruction def |
|
||||
operand = instr.getAnOperand() and
|
||||
not operand instanceof UnmodeledUseOperand and
|
||||
def = operand.getAnyDef() and
|
||||
not def.isResultModeled() and
|
||||
not def instanceof UnmodeledDefinitionInstruction and
|
||||
message =
|
||||
"Memory operand definition has unmodeled result, but is not the `UnmodeledDefinition` instruction in function '$@'" and
|
||||
func = instr.getEnclosingIRFunction() and
|
||||
funcText = Language::getIdentityString(func.getFunction())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if operand `operand` consumes a value that was defined in
|
||||
* a different function.
|
||||
|
||||
@@ -190,14 +190,15 @@ class Instruction extends Construction::TInstruction {
|
||||
final Language::Location getLocation() { result = getAST().getLocation() }
|
||||
|
||||
/**
|
||||
* Gets the `Expr` whose result is computed by this instruction, if any.
|
||||
* Gets the `Expr` whose result is computed by this instruction, if any. The `Expr` may be a
|
||||
* conversion.
|
||||
*/
|
||||
final Language::Expr getConvertedResultExpression() {
|
||||
result = Construction::getInstructionConvertedResultExpression(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the unconverted `Expr` whose result is computed by this instruction, if any.
|
||||
* Gets the unconverted form of the `Expr` whose result is computed by this instruction, if any.
|
||||
*/
|
||||
final Language::Expr getUnconvertedResultExpression() {
|
||||
result = Construction::getInstructionUnconvertedResultExpression(this)
|
||||
@@ -525,7 +526,7 @@ class ReturnValueInstruction extends ReturnInstruction {
|
||||
final Instruction getReturnValue() { result = getReturnValueOperand().getDef() }
|
||||
}
|
||||
|
||||
class ReturnIndirectionInstruction extends Instruction {
|
||||
class ReturnIndirectionInstruction extends VariableInstruction {
|
||||
ReturnIndirectionInstruction() { getOpcode() instanceof Opcode::ReturnIndirection }
|
||||
|
||||
final SideEffectOperand getSideEffectOperand() { result = getAnOperand() }
|
||||
@@ -535,6 +536,12 @@ class ReturnIndirectionInstruction extends Instruction {
|
||||
final AddressOperand getSourceAddressOperand() { result = getAnOperand() }
|
||||
|
||||
final Instruction getSourceAddress() { result = getSourceAddressOperand().getDef() }
|
||||
|
||||
/**
|
||||
* Gets the parameter for which this instruction reads the final pointed-to value within the
|
||||
* function.
|
||||
*/
|
||||
final Language::Parameter getParameter() { result = var.(IRUserVariable).getVariable() }
|
||||
}
|
||||
|
||||
class CopyInstruction extends Instruction {
|
||||
|
||||
@@ -14,8 +14,7 @@ int getConstantValue(Instruction instr) {
|
||||
or
|
||||
exists(PhiInstruction phi |
|
||||
phi = instr and
|
||||
result = max(Operand op | op = phi.getAnInputOperand() | getConstantValue(op.getDef())) and
|
||||
result = min(Operand op | op = phi.getAnInputOperand() | getConstantValue(op.getDef()))
|
||||
result = unique(Operand op | op = phi.getAnInputOperand() | getConstantValue(op.getDef()))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,11 +6,12 @@ private import DebugSSA
|
||||
|
||||
bindingset[offset]
|
||||
private string getKeySuffixForOffset(int offset) {
|
||||
offset >= 0 and
|
||||
if offset % 2 = 0 then result = "" else result = "_Chi"
|
||||
}
|
||||
|
||||
bindingset[offset]
|
||||
private int getIndexForOffset(int offset) { result = offset / 2 }
|
||||
private int getIndexForOffset(int offset) { offset >= 0 and result = offset / 2 }
|
||||
|
||||
/**
|
||||
* Property provide that dumps the memory access of each result. Useful for debugging SSA
|
||||
|
||||
@@ -86,9 +86,15 @@ predicate hasUnsignedIntegerType(int byteSize) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if an `IRFloatingPointType` with the specified `byteSize` should exist.
|
||||
* Holds if an `IRFloatingPointType` with the specified size, base, and type domain should exist.
|
||||
*/
|
||||
predicate hasFloatingPointType(int byteSize) { byteSize = any(FloatingPointType type).getSize() }
|
||||
predicate hasFloatingPointType(int byteSize, int base, TypeDomain domain) {
|
||||
exists(FloatingPointType type |
|
||||
byteSize = type.getSize() and
|
||||
base = type.getBase() and
|
||||
domain = type.getDomain()
|
||||
)
|
||||
}
|
||||
|
||||
private predicate isPointerIshType(Type type) {
|
||||
type instanceof PointerType
|
||||
@@ -159,8 +165,13 @@ private IRType getIRTypeForPRValue(Type type) {
|
||||
isUnsignedIntegerType(unspecifiedType) and
|
||||
result.(IRUnsignedIntegerType).getByteSize() = type.getSize()
|
||||
or
|
||||
unspecifiedType instanceof FloatingPointType and
|
||||
result.(IRFloatingPointType).getByteSize() = type.getSize()
|
||||
exists(FloatingPointType floatType, IRFloatingPointType irFloatType |
|
||||
floatType = unspecifiedType and
|
||||
irFloatType = result and
|
||||
irFloatType.getByteSize() = floatType.getSize() and
|
||||
irFloatType.getBase() = floatType.getBase() and
|
||||
irFloatType.getDomain() = floatType.getDomain()
|
||||
)
|
||||
or
|
||||
isPointerIshType(unspecifiedType) and result.(IRAddressType).getByteSize() = getTypeSize(type)
|
||||
or
|
||||
@@ -438,15 +449,37 @@ CppPRValueType getCanonicalUnsignedIntegerType(int byteSize) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `CppType` that is the canonical type for an `IRFloatingPointType` with the specified
|
||||
* `byteSize`.
|
||||
* Gets the sort priority of a `RealNumberType` base on its precision.
|
||||
*/
|
||||
CppPRValueType getCanonicalFloatingPointType(int byteSize) {
|
||||
private int getPrecisionPriority(RealNumberType type) {
|
||||
// Prefer `double`, `float`, `long double` in that order.
|
||||
if type instanceof DoubleType
|
||||
then result = 4
|
||||
else
|
||||
if type instanceof FloatType
|
||||
then result = 3
|
||||
else
|
||||
if type instanceof LongDoubleType
|
||||
then result = 2
|
||||
else
|
||||
// If we get this far, prefer non-extended-precision types.
|
||||
if not type.isExtendedPrecision()
|
||||
then result = 1
|
||||
else result = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `CppType` that is the canonical type for an `IRFloatingPointType` with the specified
|
||||
* size, base, and type domain.
|
||||
*/
|
||||
CppPRValueType getCanonicalFloatingPointType(int byteSize, int base, TypeDomain domain) {
|
||||
result =
|
||||
TPRValueType(max(FloatingPointType type |
|
||||
type.getSize() = byteSize
|
||||
type.getSize() = byteSize and
|
||||
type.getBase() = base and
|
||||
type.getDomain() = domain
|
||||
|
|
||||
type order by type.toString() desc
|
||||
type order by getPrecisionPriority(type.getRealType()), type.toString() desc
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,14 @@ class LanguageType = CppType;
|
||||
|
||||
class OpaqueTypeTag = Cpp::Type;
|
||||
|
||||
class TypeDomain = Cpp::TypeDomain;
|
||||
|
||||
class RealDomain = Cpp::RealDomain;
|
||||
|
||||
class ComplexDomain = Cpp::ComplexDomain;
|
||||
|
||||
class ImaginaryDomain = Cpp::ImaginaryDomain;
|
||||
|
||||
class Function = Cpp::Function;
|
||||
|
||||
class Location = Cpp::Location;
|
||||
|
||||
@@ -23,17 +23,20 @@ Type getVariableType(Variable v) {
|
||||
then
|
||||
result = getDecayedType(declaredType)
|
||||
or
|
||||
not exists(getDecayedType(declaredType)) and result = declaredType
|
||||
not exists(getDecayedType(declaredType)) and result = v.getType()
|
||||
else
|
||||
if declaredType instanceof ArrayType and not declaredType.(ArrayType).hasArraySize()
|
||||
then
|
||||
result = v.getInitializer().getExpr().getUnspecifiedType()
|
||||
result = v.getInitializer().getExpr().getType()
|
||||
or
|
||||
not exists(v.getInitializer()) and result = declaredType
|
||||
else result = declaredType
|
||||
not exists(v.getInitializer()) and result = v.getType()
|
||||
else result = v.getType()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the database contains a `case` label with the specified minimum and maximum value.
|
||||
*/
|
||||
predicate hasCaseEdge(SwitchCase switchCase, string minValue, string maxValue) {
|
||||
minValue = switchCase.getExpr().getFullyConverted().getValue() and
|
||||
if exists(switchCase.getEndExpr())
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* Provides implementation classes modelling various methods of allocation
|
||||
* (`malloc`, `new` etc). See `semmle.code.cpp.models.interfaces.Allocation`
|
||||
* for usage information.
|
||||
*/
|
||||
|
||||
import semmle.code.cpp.models.interfaces.Allocation
|
||||
|
||||
/**
|
||||
@@ -83,6 +89,18 @@ class MallocAllocationFunction extends AllocationFunction {
|
||||
or
|
||||
// kmem_zalloc(size, flags)
|
||||
name = "kmem_zalloc" and sizeArg = 0
|
||||
or
|
||||
// CRYPTO_malloc(size_t num, const char *file, int line)
|
||||
name = "CRYPTO_malloc" and sizeArg = 0
|
||||
or
|
||||
// CRYPTO_zalloc(size_t num, const char *file, int line)
|
||||
name = "CRYPTO_zalloc" and sizeArg = 0
|
||||
or
|
||||
// CRYPTO_secure_malloc(size_t num, const char *file, int line)
|
||||
name = "CRYPTO_secure_malloc" and sizeArg = 0
|
||||
or
|
||||
// CRYPTO_secure_zalloc(size_t num, const char *file, int line)
|
||||
name = "CRYPTO_secure_zalloc" and sizeArg = 0
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -163,6 +181,9 @@ class ReallocAllocationFunction extends AllocationFunction {
|
||||
or
|
||||
// CoTaskMemRealloc(ptr, size)
|
||||
name = "CoTaskMemRealloc" and sizeArg = 1 and reallocArg = 0
|
||||
or
|
||||
// CRYPTO_realloc(void *addr, size_t num, const char *file, int line);
|
||||
name = "CRYPTO_realloc" and sizeArg = 1 and reallocArg = 0
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -249,6 +270,35 @@ class OperatorNewAllocationFunction extends AllocationFunction {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `sizeExpr` is an expression consisting of a subexpression
|
||||
* `lengthExpr` multiplied by a constant `sizeof` that is the result of a
|
||||
* `sizeof()` expression. Alternatively if there isn't a suitable `sizeof()`
|
||||
* expression, `lengthExpr = sizeExpr` and `sizeof = 1`. For example:
|
||||
* ```
|
||||
* malloc(a * 2 * sizeof(char32_t));
|
||||
* ```
|
||||
* In this case if the `sizeExpr` is the argument to `malloc`, the `lengthExpr`
|
||||
* is `a * 2` and `sizeof` is `4`.
|
||||
*/
|
||||
private predicate deconstructSizeExpr(Expr sizeExpr, Expr lengthExpr, int sizeof) {
|
||||
exists(SizeofOperator sizeofOp |
|
||||
sizeofOp = sizeExpr.(MulExpr).getAnOperand() and
|
||||
lengthExpr = sizeExpr.(MulExpr).getAnOperand() and
|
||||
not lengthExpr instanceof SizeofOperator and
|
||||
sizeof = sizeofOp.getValue().toInt()
|
||||
)
|
||||
or
|
||||
not exists(SizeofOperator sizeofOp, Expr lengthOp |
|
||||
sizeofOp = sizeExpr.(MulExpr).getAnOperand() and
|
||||
lengthOp = sizeExpr.(MulExpr).getAnOperand() and
|
||||
not lengthOp instanceof SizeofOperator and
|
||||
exists(sizeofOp.getValue().toInt())
|
||||
) and
|
||||
lengthExpr = sizeExpr and
|
||||
sizeof = 1
|
||||
}
|
||||
|
||||
/**
|
||||
* An allocation expression that is a function call, such as call to `malloc`.
|
||||
*/
|
||||
@@ -266,7 +316,17 @@ class CallAllocationExpr extends AllocationExpr, FunctionCall {
|
||||
not exists(NewOrNewArrayExpr new | new.getAllocatorCall() = this)
|
||||
}
|
||||
|
||||
override Expr getSizeExpr() { result = getArgument(target.getSizeArg()) }
|
||||
override Expr getSizeExpr() {
|
||||
exists(Expr sizeExpr | sizeExpr = getArgument(target.getSizeArg()) |
|
||||
if exists(target.getSizeMult())
|
||||
then result = sizeExpr
|
||||
else
|
||||
exists(Expr lengthExpr |
|
||||
deconstructSizeExpr(sizeExpr, lengthExpr, _) and
|
||||
result = lengthExpr
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override int getSizeMult() {
|
||||
// malloc with multiplier argument that is a constant
|
||||
@@ -274,13 +334,19 @@ class CallAllocationExpr extends AllocationExpr, FunctionCall {
|
||||
or
|
||||
// malloc with no multiplier argument
|
||||
not exists(target.getSizeMult()) and
|
||||
result = 1
|
||||
deconstructSizeExpr(getArgument(target.getSizeArg()), _, result)
|
||||
}
|
||||
|
||||
override int getSizeBytes() { result = getSizeExpr().getValue().toInt() * getSizeMult() }
|
||||
|
||||
override Expr getReallocPtr() { result = getArgument(target.getReallocPtrArg()) }
|
||||
|
||||
override Type getAllocatedElementType() {
|
||||
result =
|
||||
this.getFullyConverted().getType().stripTopLevelSpecifiers().(PointerType).getBaseType() and
|
||||
not result instanceof VoidType
|
||||
}
|
||||
|
||||
override predicate requiresDealloc() { target.requiresDealloc() }
|
||||
}
|
||||
|
||||
@@ -292,6 +358,8 @@ class NewAllocationExpr extends AllocationExpr, NewExpr {
|
||||
|
||||
override int getSizeBytes() { result = getAllocatedType().getSize() }
|
||||
|
||||
override Type getAllocatedElementType() { result = getAllocatedType() }
|
||||
|
||||
override predicate requiresDealloc() { not exists(getPlacementPointer()) }
|
||||
}
|
||||
|
||||
@@ -312,6 +380,8 @@ class NewArrayAllocationExpr extends AllocationExpr, NewArrayExpr {
|
||||
result = getAllocatedElementType().getSize()
|
||||
}
|
||||
|
||||
override Type getAllocatedElementType() { result = NewArrayExpr.super.getAllocatedElementType() }
|
||||
|
||||
override int getSizeBytes() { result = getAllocatedType().getSize() }
|
||||
|
||||
override predicate requiresDealloc() { not exists(getPlacementPointer()) }
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import semmle.code.cpp.models.interfaces.Allocation
|
||||
/**
|
||||
* Provides implementation classes modelling various methods of deallocation
|
||||
* (`free`, `delete` etc). See `semmle.code.cpp.models.interfaces.Deallocation`
|
||||
* for usage information.
|
||||
*/
|
||||
|
||||
import semmle.code.cpp.models.interfaces.Deallocation
|
||||
|
||||
/**
|
||||
* A deallocation function such as `free`.
|
||||
@@ -13,6 +19,10 @@ class StandardDeallocationFunction extends DeallocationFunction {
|
||||
name = "free" and freedArg = 0
|
||||
or
|
||||
name = "realloc" and freedArg = 0
|
||||
or
|
||||
name = "CRYPTO_free" and freedArg = 0
|
||||
or
|
||||
name = "CRYPTO_secure_free" and freedArg = 0
|
||||
)
|
||||
or
|
||||
hasGlobalOrStdName(name) and
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import semmle.code.cpp.models.interfaces.Alias
|
||||
import semmle.code.cpp.models.interfaces.FlowSource
|
||||
|
||||
class Fread extends AliasFunction {
|
||||
class Fread extends AliasFunction, RemoteFlowFunction {
|
||||
Fread() { this.hasGlobalName("fread") }
|
||||
|
||||
override predicate parameterNeverEscapes(int n) {
|
||||
@@ -11,4 +12,9 @@ class Fread extends AliasFunction {
|
||||
override predicate parameterEscapesOnlyViaReturn(int n) { none() }
|
||||
|
||||
override predicate parameterIsAlwaysReturned(int n) { none() }
|
||||
|
||||
override predicate hasRemoteFlowSource(FunctionOutput output, string description) {
|
||||
output.isParameterDeref(0) and
|
||||
description = "String read by " + this.getName()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,13 @@ import semmle.code.cpp.models.interfaces.Taint
|
||||
import semmle.code.cpp.models.interfaces.ArrayFunction
|
||||
import semmle.code.cpp.models.interfaces.Alias
|
||||
import semmle.code.cpp.models.interfaces.SideEffect
|
||||
import semmle.code.cpp.models.interfaces.FlowSource
|
||||
|
||||
/**
|
||||
* The standard functions `gets` and `fgets`.
|
||||
*/
|
||||
class GetsFunction extends DataFlowFunction, TaintFunction, ArrayFunction, AliasFunction,
|
||||
SideEffectFunction {
|
||||
SideEffectFunction, RemoteFlowFunction {
|
||||
GetsFunction() {
|
||||
exists(string name | hasGlobalOrStdName(name) |
|
||||
name = "gets" or // gets(str)
|
||||
@@ -42,4 +43,9 @@ class GetsFunction extends DataFlowFunction, TaintFunction, ArrayFunction, Alias
|
||||
buffer = true and
|
||||
mustWrite = true
|
||||
}
|
||||
|
||||
override predicate hasRemoteFlowSource(FunctionOutput output, string description) {
|
||||
output.isParameterDeref(0) and
|
||||
description = "String read by " + this.getName()
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user