C++: Remove DefaultTaintTracking library

This commit is contained in:
Jeroen Ketema
2023-11-24 16:52:32 +01:00
parent e438671846
commit c02a732632
24 changed files with 4 additions and 2568 deletions

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* The deprecated `DefaultTaintTracking` library has been removed.

View File

@@ -1,21 +0,0 @@
/**
* DEPRECATED: Use `semmle.code.cpp.ir.dataflow.TaintTracking` as a replacement.
*
* An IR taint tracking library that uses an IR DataFlow configuration to track
* taint from user inputs as defined by `semmle.code.cpp.security.Security`.
*/
import cpp
import semmle.code.cpp.security.Security
private import semmle.code.cpp.ir.dataflow.internal.DefaultTaintTrackingImpl as DefaultTaintTrackingImpl
deprecated predicate predictableOnlyFlow = DefaultTaintTrackingImpl::predictableOnlyFlow/1;
deprecated predicate tainted = DefaultTaintTrackingImpl::tainted/2;
deprecated predicate taintedIncludingGlobalVars =
DefaultTaintTrackingImpl::taintedIncludingGlobalVars/3;
deprecated predicate globalVarFromId = DefaultTaintTrackingImpl::globalVarFromId/1;
deprecated module TaintedWithPath = DefaultTaintTrackingImpl::TaintedWithPath;

View File

@@ -1,668 +0,0 @@
/**
* INTERNAL: Do not use.
*
* An IR taint tracking library that uses an IR DataFlow configuration to track
* taint from user inputs as defined by `semmle.code.cpp.security.Security`.
*/
import cpp
import semmle.code.cpp.security.Security
private import semmle.code.cpp.ir.dataflow.DataFlow
private import semmle.code.cpp.ir.dataflow.internal.DataFlowUtil
private import semmle.code.cpp.ir.IR
private import semmle.code.cpp.ir.dataflow.ResolveCall
private import semmle.code.cpp.controlflow.IRGuards
private import semmle.code.cpp.models.interfaces.Taint
private import semmle.code.cpp.models.interfaces.DataFlow
private import semmle.code.cpp.ir.dataflow.TaintTracking
private import semmle.code.cpp.ir.dataflow.TaintTracking2
private import semmle.code.cpp.ir.dataflow.TaintTracking3
private import semmle.code.cpp.ir.dataflow.internal.ModelUtil
private import semmle.code.cpp.ir.dataflow.internal.DataFlowPrivate
/**
* A predictable instruction is one where an external user can predict
* the value. For example, a literal in the source code is considered
* predictable.
*/
private predicate predictableInstruction(Instruction instr) {
instr instanceof ConstantInstruction
or
instr instanceof StringConstantInstruction
or
// This could be a conversion on a string literal
predictableInstruction(instr.(UnaryInstruction).getUnary())
}
/**
* Functions that we should only allow taint to flow through (to the return
* value) if all but the source argument are 'predictable'. This is done to
* emulate the old security library's implementation rather than due to any
* strong belief that this is the right approach.
*
* Note that the list itself is not very principled; it consists of all the
* functions listed in the old security library's [default] `isPureFunction`
* that have more than one argument, but are not in the old taint tracking
* library's `returnArgument` predicate.
*/
predicate predictableOnlyFlow(string name) {
name =
[
"strcasestr", "strchnul", "strchr", "strchrnul", "strcmp", "strcspn", "strncmp", "strndup",
"strnlen", "strrchr", "strspn", "strstr", "strtod", "strtof", "strtol", "strtoll", "strtoq",
"strtoul"
]
}
private DataFlow::Node getNodeForSource(Expr source) {
isUserInput(source, _) and
result = getNodeForExpr(source)
}
private DataFlow::Node getNodeForExpr(Expr node) {
node = DataFlow::ExprFlowCached::asExprInternal(result)
or
// Some of the sources in `isUserInput` are intended to match the value of
// an expression, while others (those modeled below) are intended to match
// the taint that propagates out of an argument, like the `char *` argument
// to `gets`. It's impossible here to tell which is which, but the "access
// to argv" source is definitely not intended to match an output argument,
// and it causes false positives if we let it.
//
// This case goes together with the similar (but not identical) rule in
// `nodeIsBarrierIn`.
result = DataFlow::definitionByReferenceNodeFromArgument(node) and
not argv(node.(VariableAccess).getTarget())
}
private predicate conflatePointerAndPointee(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
// Flow from `op` to `*op`.
exists(Operand operand, int indirectionIndex |
nodeHasOperand(nodeFrom, operand, indirectionIndex) and
nodeHasOperand(nodeTo, operand, indirectionIndex - 1)
)
or
// Flow from `instr` to `*instr`.
exists(Instruction instr, int indirectionIndex |
nodeHasInstruction(nodeFrom, instr, indirectionIndex) and
nodeHasInstruction(nodeTo, instr, indirectionIndex - 1)
)
}
private module DefaultTaintTrackingConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source = getNodeForSource(_) }
predicate isSink(DataFlow::Node sink) { exists(adjustedSink(sink)) }
predicate isBarrier(DataFlow::Node node) { nodeIsBarrier(node) }
predicate isBarrierIn(DataFlow::Node node) { nodeIsBarrierIn(node) }
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
conflatePointerAndPointee(nodeFrom, nodeTo)
}
}
private module DefaultTaintTrackingFlow = TaintTracking::Global<DefaultTaintTrackingConfig>;
private module ToGlobalVarTaintTrackingConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source = getNodeForSource(_) }
predicate isSink(DataFlow::Node sink) { sink.asVariable() instanceof GlobalOrNamespaceVariable }
predicate isAdditionalFlowStep(DataFlow::Node n1, DataFlow::Node n2) {
writesVariable(n1.asInstruction(), n2.asVariable().(GlobalOrNamespaceVariable))
or
readsVariable(n2.asInstruction(), n1.asVariable().(GlobalOrNamespaceVariable))
}
predicate isBarrier(DataFlow::Node node) { nodeIsBarrier(node) }
predicate isBarrierIn(DataFlow::Node node) { nodeIsBarrierIn(node) }
}
private module ToGlobalVarTaintTrackingFlow = TaintTracking::Global<ToGlobalVarTaintTrackingConfig>;
private module FromGlobalVarTaintTrackingConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
// This set of sources should be reasonably small, which is good for
// performance since the set of sinks is very large.
ToGlobalVarTaintTrackingFlow::flowTo(source)
}
predicate isSink(DataFlow::Node sink) { exists(adjustedSink(sink)) }
predicate isAdditionalFlowStep(DataFlow::Node n1, DataFlow::Node n2) {
// Additional step for flow out of variables. There is no flow _into_
// variables in this configuration, so this step only serves to take flow
// out of a variable that's a source.
readsVariable(n2.asInstruction(), n1.asVariable())
}
predicate isBarrier(DataFlow::Node node) { nodeIsBarrier(node) }
predicate isBarrierIn(DataFlow::Node node) { nodeIsBarrierIn(node) }
}
private module FromGlobalVarTaintTrackingFlow =
TaintTracking::Global<FromGlobalVarTaintTrackingConfig>;
private predicate readsVariable(LoadInstruction load, Variable var) {
load.getSourceAddress().(VariableAddressInstruction).getAstVariable() = var
}
private predicate writesVariable(StoreInstruction store, Variable var) {
store.getDestinationAddress().(VariableAddressInstruction).getAstVariable() = var
}
/**
* 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
// variable _access_ is guarded by an upper-bound check. We probably don't want
// to do this right away since it could expose a lot of FPs that were
// previously suppressed by this predicate by coincidence.
private predicate hasUpperBoundsCheck(Variable var) {
exists(RelationalOperation oper, VariableAccess access |
oper.getAnOperand() = access and
access.getTarget() = var and
// Comparing to 0 is not an upper bound check
not oper.getAnOperand().getValue() = "0"
)
}
private predicate nodeIsBarrierEqualityCandidate(
DataFlow::Node node, Operand access, Variable checkedVar
) {
exists(Instruction instr | instr = node.asOperand().getDef() |
readsVariable(instr, checkedVar) and
any(IRGuardCondition guard).ensuresEq(access, _, _, instr.getBlock(), true)
)
}
cached
private module Cached {
cached
predicate nodeIsBarrier(DataFlow::Node node) {
exists(Variable checkedVar, Instruction instr | instr = node.asOperand().getDef() |
readsVariable(instr, checkedVar) and
hasUpperBoundsCheck(checkedVar)
)
or
exists(Variable checkedVar, Operand access |
/*
* This node is guarded by a condition that forces the accessed variable
* to equal something else. For example:
* ```
* x = taintsource()
* if (x == 10) {
* taintsink(x); // not considered tainted
* }
* ```
*/
nodeIsBarrierEqualityCandidate(node, access, checkedVar) and
readsVariable(access.getDef(), checkedVar)
)
}
cached
predicate nodeIsBarrierIn(DataFlow::Node node) {
// don't use dataflow into taint sources, as this leads to duplicate results.
exists(Expr source | isUserInput(source, _) |
source = DataFlow::ExprFlowCached::asExprInternal(node)
or
// This case goes together with the similar (but not identical) rule in
// `getNodeForSource`.
node = DataFlow::definitionByReferenceNodeFromArgument(source)
)
or
// don't use dataflow into binary instructions if both operands are unpredictable
exists(BinaryInstruction iTo |
iTo = node.asInstruction() and
not predictableInstruction(iTo.getLeft()) and
not predictableInstruction(iTo.getRight()) and
// propagate taint from either the pointer or the offset, regardless of predictability
not iTo instanceof PointerArithmeticInstruction
)
or
// don't use dataflow through calls to pure functions if two or more operands
// are unpredictable
exists(Instruction iFrom1, Instruction iFrom2, CallInstruction iTo |
iTo = node.asInstruction() and
isPureFunction(iTo.getStaticCallTarget().getName()) and
iFrom1 = iTo.getAnArgument() and
iFrom2 = iTo.getAnArgument() and
not predictableInstruction(iFrom1) and
not predictableInstruction(iFrom2) and
iFrom1 != iFrom2
)
}
cached
Element adjustedSink(DataFlow::Node sink) {
// TODO: is it more appropriate to use asConvertedExpr here and avoid
// `getConversion*`? Or will that cause us to miss some cases where there's
// flow to a conversion (like a `ReferenceDereferenceExpr`) and we want to
// pretend there was flow to the converted `Expr` for the sake of
// compatibility.
sink.asExpr().getConversion*() = result
or
// For compatibility, send flow from arguments to parameters, even for
// functions with no body.
exists(FunctionCall call, int i |
sink.asExpr() = call.getArgument(pragma[only_bind_into](i)) and
result = resolveCall(call).getParameter(pragma[only_bind_into](i))
)
or
// For compatibility, send flow into a `Variable` if there is flow to any
// Load or Store of that variable.
exists(CopyInstruction copy |
copy.getSourceValue() = sink.asInstruction() and
(
readsVariable(copy, result) or
writesVariable(copy, result)
) and
not hasUpperBoundsCheck(result)
)
or
// For compatibility, send flow into a `NotExpr` even if it's part of a
// short-circuiting condition and thus might get skipped.
result.(NotExpr).getOperand() = sink.asExpr()
or
// Taint postfix and prefix crement operations when their operand is tainted.
result.(CrementOperation).getAnOperand() = sink.asExpr()
or
// Taint `e1 += e2`, `e &= e2` and friends when `e1` or `e2` is tainted.
result.(AssignOperation).getAnOperand() = sink.asExpr()
or
result =
sink.asOperand()
.(SideEffectOperand)
.getUse()
.(ReadSideEffectInstruction)
.getArgumentDef()
.getUnconvertedResultExpression()
}
/**
* Step to return value of a modeled function when an input taints the
* dereference of the return value.
*/
cached
predicate additionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) {
exists(CallInstruction call, Function func, FunctionInput modelIn, FunctionOutput modelOut |
n1 = callInput(call, modelIn) and
(
func.(TaintFunction).hasTaintFlow(modelIn, modelOut)
or
func.(DataFlowFunction).hasDataFlow(modelIn, modelOut)
) and
call.getStaticCallTarget() = func and
modelOut.isReturnValueDeref() and
call = n2.asInstruction()
)
}
}
private import Cached
/**
* 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(DataFlow::Node sink |
DefaultTaintTrackingFlow::flow(getNodeForSource(source), sink) and
tainted = adjustedSink(sink)
)
}
/**
* 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
globalVar = ""
or
exists(
DataFlow::VariableNode variableNode, GlobalOrNamespaceVariable global, DataFlow::Node sink
|
global = variableNode.getVariable() and
ToGlobalVarTaintTrackingFlow::flow(getNodeForSource(source), variableNode) and
FromGlobalVarTaintTrackingFlow::flow(variableNode, sink) and
tainted = adjustedSink(sink) and
global = globalVarFromId(globalVar)
)
}
/**
* 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() }
/**
* 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 sources in this configuration. */
predicate isSource(Expr source) { exists(getNodeForSource(source)) }
/** Override this to specify which elements are sinks in this configuration. */
abstract predicate isSink(Element e);
/** Override this to specify which expressions are barriers in this configuration. */
predicate isBarrier(Expr e) { nodeIsBarrier(getNodeForExpr(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 module AdjustedConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
exists(TaintTrackingConfiguration cfg, Expr e |
cfg.isSource(e) and source = getNodeForExpr(e)
)
}
predicate isSink(DataFlow::Node sink) {
exists(TaintTrackingConfiguration cfg | cfg.isSink(adjustedSink(sink)))
}
predicate isAdditionalFlowStep(DataFlow::Node n1, DataFlow::Node n2) {
conflatePointerAndPointee(n1, n2)
or
// Steps into and out of global variables
exists(TaintTrackingConfiguration cfg | cfg.taintThroughGlobals() |
writesVariable(n1.asInstruction(), n2.asVariable().(GlobalOrNamespaceVariable))
or
readsVariable(n2.asInstruction(), n1.asVariable().(GlobalOrNamespaceVariable))
)
or
additionalTaintStep(n1, n2)
}
predicate isBarrier(DataFlow::Node node) {
exists(TaintTrackingConfiguration cfg, Expr e | cfg.isBarrier(e) and node = getNodeForExpr(e))
}
predicate isBarrierIn(DataFlow::Node node) { nodeIsBarrierIn(node) }
predicate neverSkip(Node node) { none() }
}
private module AdjustedFlow = TaintTracking::Global<AdjustedConfig>;
/*
* 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(AdjustedFlow::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(DataFlow::Node sourceNode, DataFlow::Node sinkNode |
AdjustedFlow::flow(sourceNode, sinkNode)
|
sourceNode = getNodeForExpr(e) and
exists(TaintTrackingConfiguration ttCfg | ttCfg.isSource(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://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
none()
}
}
/**
* INTERNAL: Do not use.
*/
module Private {
/** Gets a predecessor `PathNode` of `pathNode`, if any. */
PathNode getAPredecessor(PathNode pathNode) { edges(result, pathNode) }
/** Gets the element that `pathNode` wraps, if any. */
Element getElementFromPathNode(PathNode pathNode) {
exists(DataFlow::Node node | node = pathNode.(WrapPathNode).inner().getNode() |
result = node.asInstruction().getAst()
or
result = node.asOperand().getDef().getAst()
)
or
result = pathNode.(EndpointPathNode).inner()
}
}
private class WrapPathNode extends PathNode, TWrapPathNode {
AdjustedFlow::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(TaintTrackingConfiguration cfg | cfg.isSource(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) {
AdjustedFlow::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 |
AdjustedFlow::PathGraph::edges(a.(WrapPathNode).inner(), sinkNode.inner()) and
b.(FinalPathNode).inner() = adjustedSink(sinkNode.inner().getNode())
)
or
// Same for the first node
exists(WrapPathNode sourceNode |
AdjustedFlow::PathGraph::edges(sourceNode.inner(), b.(WrapPathNode).inner()) and
sourceNode.inner().getNode() = getNodeForExpr(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 |
AdjustedFlow::PathGraph::edges(sourceNode.inner(), sinkNode.inner()) and
sourceNode.inner().getNode() = getNodeForExpr(a.(InitialPathNode).inner()) and
b.(FinalPathNode).inner() = adjustedSink(sinkNode.inner().getNode())
)
}
/**
* Holds if there is flow from `arg` to `out` across a call that can by summarized by the flow
* from `par` to `ret` within it, in the graph of data flow path explanations.
*/
query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) {
AdjustedFlow::PathGraph::subpaths(arg.(WrapPathNode).inner(), par.(WrapPathNode).inner(),
ret.(WrapPathNode).inner(), out.(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 |
AdjustedFlow::PathGraph::subpaths(arg.(WrapPathNode).inner(), par.(WrapPathNode).inner(),
ret.(WrapPathNode).inner(), sinkNode.inner()) and
out.(FinalPathNode).inner() = adjustedSink(sinkNode.inner().getNode())
)
or
// Same for the first node
exists(WrapPathNode sourceNode |
AdjustedFlow::PathGraph::subpaths(sourceNode.inner(), par.(WrapPathNode).inner(),
ret.(WrapPathNode).inner(), out.(WrapPathNode).inner()) and
sourceNode.inner().getNode() = getNodeForExpr(arg.(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 |
AdjustedFlow::PathGraph::subpaths(sourceNode.inner(), par.(WrapPathNode).inner(),
ret.(WrapPathNode).inner(), sinkNode.inner()) and
sourceNode.inner().getNode() = getNodeForExpr(arg.(InitialPathNode).inner()) and
out.(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(DataFlow::Node flowSource, DataFlow::Node flowSink |
source = sourceNode.(InitialPathNode).inner() and
flowSource = getNodeForExpr(source) and
AdjustedFlow::flow(flowSource, flowSink) and
tainted = adjustedSink(flowSink) and
tainted = sinkNode.(FinalPathNode).inner()
)
}
private predicate isGlobalVariablePathNode(WrapPathNode n) {
n.inner().getNode().asVariable() instanceof GlobalOrNamespaceVariable
or
n.inner().getNode().asIndirectVariable() 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 |
AdjustedConfig::isSource(sourceNode.(WrapPathNode).inner().getNode()) and
edgesWithoutGlobals+(sourceNode, sinkNode) and
tainted = sinkNode.inner()
)
}
}

View File

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

View File

@@ -1,654 +0,0 @@
/**
* DEPRECATED: we now use `semmle.code.cpp.ir.dataflow.DefaultTaintTracking`,
* which is based on the IR but designed to behave similarly to this old
* library.
*
* Provides the implementation of `semmle.code.cpp.security.TaintTracking`. Do
* not import this file directly.
*/
import cpp
import Security
/** Expressions that change the value of a variable */
private predicate valueSource(Expr expr) {
exists(AssignExpr ae | expr = ae.getLValue())
or
exists(FunctionCall fc, int i |
userInputArgument(fc, i) and
expr = fc.getArgument(i)
)
or
exists(FunctionCall c, int arg |
copyValueBetweenArguments(c.getTarget(), _, arg) and
expr = c.getArgument(arg)
)
or
exists(FunctionCall c, int arg |
c.getTarget().getParameter(arg).getType() instanceof ReferenceType and
expr = c.getArgument(arg)
)
}
/** Expressions that are inside an expression that changes the value of a variable */
private predicate insideValueSource(Expr expr) {
valueSource(expr)
or
insideValueSource(expr.getParent()) and
// A modification of array[offset] does not modify offset
not expr.getParent().(ArrayExpr).getArrayOffset() = expr
}
private predicate isPointer(Type type) {
type instanceof PointerType or
isPointer(type.(ReferenceType).getBaseType())
}
/**
* Tracks data flow from src to dest.
* If this is used in the left side of an assignment src and dest should be swapped
*/
private predicate moveToDependingOnSide(Expr src, Expr dest) {
exists(ParenthesisExpr e |
src = e.getAChild() and
dest = e
)
or
exists(ArrayExpr e |
src = e.getArrayBase() and
dest = e
)
or
exists(PointerDereferenceExpr e |
src = e.getOperand() and
dest = e
)
or
exists(AddressOfExpr e |
src = e.getOperand() and
dest = e
)
or
// if var+offset is tainted, then so is var
exists(VariableAccess base, BinaryOperation binop |
dest = binop and
(base = binop.getLeftOperand() or base = binop.getRightOperand()) and
isPointer(base.getType()) and
base.getTarget() instanceof LocalScopeVariable and
src = base and
// flow through pointer-pointer subtraction is dubious, the result should be
// a number bounded by the size of the pointed-to thing.
not binop instanceof PointerDiffExpr
)
or
exists(UnaryOperation unop |
dest = unop and
unop.getAnOperand() = src
)
or
exists(BinaryOperation binop |
dest = binop and
binop.getLeftOperand() = src and
predictable(binop.getRightOperand())
)
or
exists(BinaryOperation binop |
dest = binop and
binop.getRightOperand() = src and
predictable(binop.getLeftOperand())
)
or
exists(Cast cast |
dest = cast and
src = cast.getExpr()
)
or
exists(ConditionalExpr cond |
cond = dest and
(
cond.getThen() = src or
cond.getElse() = src
)
)
}
/**
* Track value flow between functions.
* Handles the following cases:
* - If an argument to a function is tainted, all the usages of the parameter inside the function are tainted
* - If a function obtains input from the user internally and returns it, all calls to the function are tainted
* - If an argument to a function is tainted and that parameter is returned, all calls to the function are not tainted
* (this is done to avoid false positives). Because of this we need to track if the tainted element came from an argument
* or not, and for that we use destFromArg
*/
deprecated private predicate betweenFunctionsValueMoveTo(
Element src, Element dest, boolean destFromArg
) {
not unreachable(src) and
not unreachable(dest) and
(
exists(Call call, int i |
src = call.getArgument(i) and
resolveCallWithParam(call, _, i, dest) and
destFromArg = true
)
or
// Only move the return of the function to the function itself if the value didn't came from an
// argument, or else we would taint all the calls to one function if one argument is tainted
// somewhere
exists(Function f, ReturnStmt ret |
ret.getEnclosingFunction() = f and
src = ret.getExpr() and
destFromArg = false and
dest = f
)
or
exists(Call call, Function f |
f = resolveCall(call) and
src = f and
dest = call and
destFromArg = false
)
or
// If a parameter of type reference is tainted inside a function, taint the argument too
exists(Call call, int pi, Parameter p |
resolveCallWithParam(call, _, pi, p) and
p.getType() instanceof ReferenceType and
src = p and
dest = call.getArgument(pi) and
destFromArg = false
)
)
}
// predicate folding for proper join-order
// bad magic: pushes down predicate that ruins join-order
pragma[nomagic]
deprecated private predicate resolveCallWithParam(Call call, Function called, int i, Parameter p) {
called = resolveCall(call) and
p = called.getParameter(i)
}
/** A variable for which flow through is allowed. */
deprecated library class FlowVariable extends Variable {
FlowVariable() {
(
this instanceof LocalScopeVariable or
this instanceof GlobalOrNamespaceVariable
) and
not argv(this)
}
}
/** A local scope variable for which flow through is allowed. */
deprecated library class FlowLocalScopeVariable extends Variable {
FlowLocalScopeVariable() { this instanceof LocalScopeVariable }
}
deprecated private predicate insideFunctionValueMoveTo(Element src, Element dest) {
not unreachable(src) and
not unreachable(dest) and
(
// Taint all variable usages when one is tainted
// This function taints global variables but doesn't taint from a global variable (see globalVariableValueMoveTo)
exists(FlowLocalScopeVariable v |
src = v and
dest = v.getAnAccess() and
not insideValueSource(dest)
)
or
exists(FlowVariable v |
src = v.getAnAccess() and
dest = v and
insideValueSource(src)
)
or
// Taint all union usages when one is tainted
// This function taints global variables but doesn't taint from a global variable (see globalVariableValueMoveTo)
exists(FlowLocalScopeVariable v, FieldAccess a |
unionAccess(v, _, a) and
src = v and
dest = a and
not insideValueSource(dest)
)
or
exists(FlowVariable v, FieldAccess a |
unionAccess(v, _, a) and
src = a and
dest = v and
insideValueSource(src)
)
or
// If a pointer is tainted, taint the original variable
exists(FlowVariable p, FlowVariable v, AddressOfExpr e |
p.getAnAssignedValue() = e and
e.getOperand() = v.getAnAccess() and
src = p and
dest = v
)
or
// If a reference is tainted, taint the original variable
exists(FlowVariable r, FlowVariable v |
r.getType() instanceof ReferenceType and
r.getInitializer().getExpr() = v.getAnAccess() and
src = r and
dest = v
)
or
exists(Variable var |
var = dest and
var.getInitializer().getExpr() = src
)
or
exists(AssignExpr ae |
src = ae.getRValue() and
dest = ae.getLValue()
)
or
exists(CommaExpr comma |
comma = dest and
comma.getRightOperand() = src
)
or
exists(FunctionCall c, int sourceArg, int destArg |
copyValueBetweenArguments(c.getTarget(), sourceArg, destArg) and
// Only consider copies from `printf`-like functions if the format is a string
(
exists(FormattingFunctionCall ffc, FormatLiteral format |
ffc = c and
format = ffc.getFormat() and
format.getConversionChar(sourceArg - ffc.getTarget().getNumberOfParameters()) = ["s", "S"]
)
or
not c.(FormattingFunctionCall).getFormat() instanceof FormatLiteral
or
not c instanceof FormattingFunctionCall
) and
src = c.getArgument(sourceArg) and
dest = c.getArgument(destArg)
)
or
exists(FunctionCall c, int sourceArg |
returnArgument(c.getTarget(), sourceArg) and
src = c.getArgument(sourceArg) and
dest = c
)
or
exists(FormattingFunctionCall formattingSend, int arg, FormatLiteral format |
dest = formattingSend and
formattingSend.getArgument(arg) = src and
format = formattingSend.getFormat() and
format.getConversionChar(arg - formattingSend.getTarget().getNumberOfParameters()) =
["s", "S", "@"]
)
or
// Expressions computed from tainted data are also tainted
exists(FunctionCall call | dest = call and isPureFunction(call.getTarget().getName()) |
call.getAnArgument() = src and
forall(Expr arg | arg = call.getAnArgument() | arg = src or predictable(arg)) and
// flow through `strlen` tends to cause dubious results, if the length is
// bounded.
not call.getTarget().getName() = "strlen"
)
or
exists(Element a, Element b |
moveToDependingOnSide(a, b) and
if insideValueSource(a) then (src = b and dest = a) else (src = a and dest = b)
)
)
}
/**
* Handles data flow from global variables to its usages.
* The tainting for the global variable itself is done at insideFunctionValueMoveTo.
*/
private predicate globalVariableValueMoveTo(GlobalOrNamespaceVariable src, Expr dest) {
not unreachable(dest) and
(
exists(GlobalOrNamespaceVariable v |
src = v and
dest = v.getAnAccess() and
not insideValueSource(dest)
)
or
exists(GlobalOrNamespaceVariable v, FieldAccess a |
unionAccess(v, _, a) and
src = v and
dest = a and
not insideValueSource(dest)
)
)
}
private predicate unionAccess(Variable v, Field f, FieldAccess a) {
f.getDeclaringType() instanceof Union and
a.getTarget() = f and
a.getQualifier() = v.getAnAccess()
}
deprecated GlobalOrNamespaceVariable globalVarFromId(string id) {
if result instanceof NamespaceVariable
then id = result.getNamespace() + "::" + result.getName()
else id = result.getName()
}
/**
* 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);
* ```
*/
private predicate hasUpperBoundsCheck(Variable var) {
exists(RelationalOperation oper, VariableAccess access |
oper.getAnOperand() = access and
access.getTarget() = var and
// Comparing to 0 is not an upper bound check
not oper.getAnOperand().getValue() = "0"
)
}
cached
deprecated private predicate taintedWithArgsAndGlobalVars(
Element src, Element dest, boolean destFromArg, string globalVar
) {
isUserInput(src, _) and
not unreachable(src) and
dest = src and
destFromArg = false and
globalVar = ""
or
exists(Element other, boolean otherFromArg, string otherGlobalVar |
taintedWithArgsAndGlobalVars(src, other, otherFromArg, otherGlobalVar)
|
not unreachable(dest) and
not hasUpperBoundsCheck(dest) and
(
// Direct flow from one expression to another.
betweenFunctionsValueMoveTo(other, dest, destFromArg) and
(destFromArg = true or otherFromArg = false) and
globalVar = otherGlobalVar
or
insideFunctionValueMoveTo(other, dest) and
destFromArg = otherFromArg and
globalVar = otherGlobalVar
or
exists(GlobalOrNamespaceVariable v |
v = other and
globalVariableValueMoveTo(v, dest) and
destFromArg = false and
v = globalVarFromId(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 doesn't include data flow through global variables.
* If you need that you must call taintedIncludingGlobalVars.
*/
deprecated predicate tainted(Expr source, Element tainted) {
taintedWithArgsAndGlobalVars(source, tainted, _, "")
}
/**
* 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 name of the last global variable used to move the
* value from source to tainted.
*/
deprecated predicate taintedIncludingGlobalVars(Expr source, Element tainted, string globalVar) {
taintedWithArgsAndGlobalVars(source, tainted, _, globalVar)
}
/**
* A predictable expression is one where an external user can predict
* the value. For example, a literal in the source code is considered
* predictable.
*/
private predicate predictable(Expr expr) {
expr instanceof Literal
or
exists(BinaryOperation binop | binop = expr |
predictable(binop.getLeftOperand()) and predictable(binop.getRightOperand())
)
or
exists(UnaryOperation unop | unop = expr | predictable(unop.getOperand()))
}
private int maxArgIndex(Function f) {
result =
max(FunctionCall fc, int toMax |
fc.getTarget() = f and toMax = fc.getNumberOfArguments() - 1
|
toMax
)
}
/** Functions that copy the value of one argument to another */
private predicate copyValueBetweenArguments(Function f, int sourceArg, int destArg) {
f.hasGlobalOrStdName("memcpy") and sourceArg = 1 and destArg = 0
or
f.hasGlobalName("__builtin___memcpy_chk") and sourceArg = 1 and destArg = 0
or
f.hasGlobalOrStdName("memmove") and sourceArg = 1 and destArg = 0
or
f.hasGlobalOrStdName("strcat") and sourceArg = 1 and destArg = 0
or
f.hasGlobalName("_mbscat") and sourceArg = 1 and destArg = 0
or
f.hasGlobalOrStdName("wcscat") and sourceArg = 1 and destArg = 0
or
f.hasGlobalOrStdName("strncat") and sourceArg = 1 and destArg = 0
or
f.hasGlobalName("_mbsncat") and sourceArg = 1 and destArg = 0
or
f.hasGlobalName("wcsncat") and sourceArg = 1 and destArg = 0
or
f.hasGlobalOrStdName("strcpy") and sourceArg = 1 and destArg = 0
or
f.hasGlobalName("_mbscpy") and sourceArg = 1 and destArg = 0
or
f.hasGlobalOrStdName("wcscpy") and sourceArg = 1 and destArg = 0
or
f.hasGlobalOrStdName("strncpy") and sourceArg = 1 and destArg = 0
or
f.hasGlobalName("_mbsncpy") and sourceArg = 1 and destArg = 0
or
f.hasGlobalOrStdName("wcsncpy") and sourceArg = 1 and destArg = 0
or
f.hasGlobalName("inet_aton") and sourceArg = 0 and destArg = 1
or
f.hasGlobalName("inet_pton") and sourceArg = 1 and destArg = 2
or
f.hasGlobalOrStdName("strftime") and sourceArg in [2 .. maxArgIndex(f)] and destArg = 0
or
exists(FormattingFunction ff | ff = f |
sourceArg in [ff.getFormatParameterIndex() .. maxArgIndex(f)] and
destArg = ff.getOutputParameterIndex(false)
)
}
/** Functions where if one of the arguments is tainted, the result should be tainted */
private predicate returnArgument(Function f, int sourceArg) {
f.hasGlobalName("memcpy") and sourceArg = 0
or
f.hasGlobalName("__builtin___memcpy_chk") and sourceArg = 0
or
f.hasGlobalOrStdName("memmove") and sourceArg = 0
or
f.hasGlobalOrStdName("strcat") and sourceArg = 0
or
f.hasGlobalName("_mbscat") and sourceArg = 0
or
f.hasGlobalOrStdName("wcsncat") and sourceArg = 0
or
f.hasGlobalOrStdName("strncat") and sourceArg = 0
or
f.hasGlobalName("_mbsncat") and sourceArg = 0
or
f.hasGlobalOrStdName("wcsncat") and sourceArg = 0
or
f.hasGlobalOrStdName("strcpy") and sourceArg = 0
or
f.hasGlobalName("_mbscpy") and sourceArg = 0
or
f.hasGlobalOrStdName("wcscpy") and sourceArg = 0
or
f.hasGlobalOrStdName("strncpy") and sourceArg = 0
or
f.hasGlobalName("_mbsncpy") and sourceArg = 0
or
f.hasGlobalOrStdName("wcsncpy") and sourceArg = 0
or
f.hasGlobalName("inet_ntoa") and sourceArg = 0
or
f.hasGlobalName("inet_addr") and sourceArg = 0
or
f.hasGlobalName("inet_network") and sourceArg = 0
or
f.hasGlobalName("inet_ntoa") and sourceArg = 0
or
f.hasGlobalName("inet_makeaddr") and
(sourceArg = 0 or sourceArg = 1)
or
f.hasGlobalName("inet_lnaof") and sourceArg = 0
or
f.hasGlobalName("inet_netof") and sourceArg = 0
or
f.hasGlobalName("gethostbyname") and sourceArg = 0
or
f.hasGlobalName("gethostbyaddr") and sourceArg = 0
}
/**
* 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).
*/
deprecated Function resolveCall(Call call) {
result = call.getTarget()
or
result = call.(DataSensitiveCallExpr).resolve()
}
/** A data sensitive call expression. */
abstract deprecated library class DataSensitiveCallExpr extends Expr {
DataSensitiveCallExpr() { not unreachable(this) }
abstract Expr getSrc();
cached
abstract Function resolve();
/**
* Whether `src` can flow to this call expression.
*
* Searches backwards from `getSrc()` to `src`.
*/
predicate flowsFrom(Element src, boolean allowFromArg) {
src = this.getSrc() and allowFromArg = true
or
exists(Element other, boolean allowOtherFromArg | this.flowsFrom(other, allowOtherFromArg) |
exists(boolean otherFromArg | betweenFunctionsValueMoveToStatic(src, other, otherFromArg) |
otherFromArg = true and allowOtherFromArg = true and allowFromArg = true
or
otherFromArg = false and allowFromArg = false
)
or
insideFunctionValueMoveTo(src, other) and allowFromArg = allowOtherFromArg
or
globalVariableValueMoveTo(src, other) and allowFromArg = true
)
}
}
/** Call through a function pointer. */
deprecated library class DataSensitiveExprCall extends DataSensitiveCallExpr, ExprCall {
override Expr getSrc() { result = this.getExpr() }
override Function resolve() {
exists(FunctionAccess fa | this.flowsFrom(fa, true) | result = fa.getTarget())
}
}
/** Call to a virtual function. */
deprecated library class DataSensitiveOverriddenFunctionCall extends DataSensitiveCallExpr,
FunctionCall
{
DataSensitiveOverriddenFunctionCall() {
exists(this.getTarget().(VirtualFunction).getAnOverridingFunction())
}
override Expr getSrc() { result = this.getQualifier() }
override MemberFunction resolve() {
exists(NewExpr new |
this.flowsFrom(new, true) and
memberFunctionFromNewExpr(new, result) and
result.overrides*(this.getTarget().(VirtualFunction))
)
}
}
private predicate memberFunctionFromNewExpr(NewExpr new, MemberFunction f) {
f = new.getAllocatedType().(Class).getAMemberFunction()
}
/** Same as `betweenFunctionsValueMoveTo`, but calls are resolved to their static target. */
private predicate betweenFunctionsValueMoveToStatic(Element src, Element dest, boolean destFromArg) {
not unreachable(src) and
not unreachable(dest) and
(
exists(FunctionCall call, Function called, int i |
src = call.getArgument(i) and
called = call.getTarget() and
dest = called.getParameter(i) and
destFromArg = true
)
or
// Only move the return of the function to the function itself if the value didn't came from an
// argument, or else we would taint all the calls to one function if one argument is tainted
// somewhere
exists(Function f, ReturnStmt ret |
ret.getEnclosingFunction() = f and
src = ret.getExpr() and
destFromArg = false and
dest = f
)
or
exists(FunctionCall call, Function f |
call.getTarget() = f and
src = f and
dest = call and
destFromArg = false
)
or
// If a parameter of type reference is tainted inside a function, taint the argument too
exists(FunctionCall call, Function f, int pi, Parameter p |
call.getTarget() = f and
f.getParameter(pi) = p and
p.getType() instanceof ReferenceType and
src = p and
dest = call.getArgument(pi) and
destFromArg = false
)
)
}

View File

@@ -1,74 +0,0 @@
#include "../shared.h"
using SinkFunction = void (*)(int);
void notSink(int notSinkParam);
void callsSink(int sinkParam) { // $ ir-path=31:23 ir-path=32:26 ir-path=34:17
sink(sinkParam); // $ ast=31:28 ast=32:31 ast=34:22 ir-sink
}
struct {
SinkFunction sinkPtr, notSinkPtr;
} globalStruct;
union {
SinkFunction sinkPtr, notSinkPtr;
} globalUnion;
SinkFunction globalSinkPtr;
void assignGlobals() {
globalStruct.sinkPtr = callsSink;
globalUnion.sinkPtr = callsSink;
globalSinkPtr = callsSink;
};
void testStruct() {
globalStruct.sinkPtr(atoi(getenv("TAINTED"))); // $ MISSING: ir-path,ast
globalStruct.notSinkPtr(atoi(getenv("TAINTED"))); // clean
globalUnion.sinkPtr(atoi(getenv("TAINTED"))); // $ ast ir-path
globalUnion.notSinkPtr(atoi(getenv("TAINTED"))); // $ ast ir-path
globalSinkPtr(atoi(getenv("TAINTED"))); // $ ast ir-path
}
class B {
public:
virtual void f(const char*) = 0;
};
class D1 : public B {};
class D2 : public D1 {
public:
void f(const char* p) override {}
};
class D3 : public D2 {
public:
void f(const char* p) override { // $ ir-path=58:10 ir-path=60:17 ir-path=61:28 ir-path=62:29 ir-path=63:33 SPURIOUS: ir-path=73:30
sink(p); // $ ast=58:10 ast=60:17 ast=61:28 ast=62:29 ast=63:33 ir-sink SPURIOUS: ast=73:30
}
};
void test_dynamic_cast() {
B* b = new D3();
b->f(getenv("VAR")); // $ ast ir-path
((D2*)b)->f(getenv("VAR")); // $ ast ir-path
static_cast<D2*>(b)->f(getenv("VAR")); // $ ast ir-path
dynamic_cast<D2*>(b)->f(getenv("VAR")); // $ ast ir-path
reinterpret_cast<D2*>(b)->f(getenv("VAR")); // $ ast ir-path
B* b2 = new D2();
b2->f(getenv("VAR"));
((D2*)b2)->f(getenv("VAR"));
static_cast<D2*>(b2)->f(getenv("VAR"));
dynamic_cast<D2*>(b2)->f(getenv("VAR"));
reinterpret_cast<D2*>(b2)->f(getenv("VAR"));
dynamic_cast<D3*>(b2)->f(getenv("VAR")); // $ SPURIOUS: ast ir-path
}

View File

@@ -1,4 +0,0 @@
WARNING: Module TaintedWithPath has been deprecated and may be removed in future (tainted.ql:9,8-47)
WARNING: Predicate tainted has been deprecated and may be removed in future (tainted.ql:20,49-74)
testFailures
failures

View File

@@ -1,100 +0,0 @@
/**
* This test provides the possibility to annotate elements when they are on a path of a taint flow to a sink.
* This is different when compared to the tests in `../annotate_sink`, where only sink invocations are annotated.
*/
import cpp
import semmle.code.cpp.security.TaintTrackingImpl as AstTaintTracking
import semmle.code.cpp.ir.dataflow.DefaultTaintTracking as IRDefaultTaintTracking
import IRDefaultTaintTracking::TaintedWithPath as TaintedWithPath
import TaintedWithPath::Private
import TestUtilities.InlineExpectationsTest
predicate isSinkArgument(Element sink) {
exists(FunctionCall call |
call.getTarget().getName() = "sink" and
sink = call.getAnArgument()
)
}
predicate astTaint(Expr source, Element sink) { AstTaintTracking::tainted(source, sink) }
class SourceConfiguration extends TaintedWithPath::TaintTrackingConfiguration {
override predicate isSink(Element e) { isSinkArgument(e) }
}
predicate irTaint(Element source, TaintedWithPath::PathNode predNode, string tag) {
exists(TaintedWithPath::PathNode sinkNode |
TaintedWithPath::taintedWithPath(source, _, _, sinkNode) and
predNode = getAPredecessor*(sinkNode) and
// Make sure the path is actually reachable from this predecessor.
// Otherwise, we could pick `predNode` to be b when `source` is
// `source1` in this dataflow graph:
// source1 ---> a ---> c ---> sinkNode
// ^
// source2 ---> b --/
source = getElementFromPathNode(getAPredecessor*(predNode)) and
if predNode = sinkNode then tag = "ir-sink" else tag = "ir-path"
)
}
module IRDefaultTaintTrackingTest implements TestSig {
string getARelevantTag() { result = ["ir-path", "ir-sink"] }
predicate hasActualResult(Location location, string element, string tag, string value) {
exists(Element elem, TaintedWithPath::PathNode node, int n |
irTaint(_, node, tag) and
elem = getElementFromPathNode(node) and
n = count(int startline | getAPredecessor(node).hasLocationInfo(_, startline, _, _, _)) and
location = elem.getLocation() and
element = elem.toString()
|
// Zero predecessors means it's a source, and 1 predecessor means it has a unique predecessor.
// In either of these cases we leave out the location.
n = [0, 1] and value = ""
or
// If there is more than one predecessor for this node
// we specify the source location explicitly.
n > 1 and
exists(TaintedWithPath::PathNode pred | pred = getAPredecessor(node) |
value =
getElementFromPathNode(pred).getLocation().getStartLine().toString() + ":" +
getElementFromPathNode(pred).getLocation().getStartColumn()
)
)
}
}
module AstTaintTrackingTest implements TestSig {
string getARelevantTag() { result = "ast" }
predicate hasActualResult(Location location, string element, string tag, string value) {
exists(Expr source, Element tainted, int n |
tag = "ast" and
astTaint(source, tainted) and
(
isSinkArgument(tainted)
or
exists(Element sink |
isSinkArgument(sink) and
astTaint(tainted, sink)
)
) and
n = strictcount(Expr otherSource | astTaint(otherSource, tainted)) and
(
n = 1 and value = ""
or
// If there is more than one source for this sink
// we specify the source location explicitly.
n > 1 and
value =
source.getLocation().getStartLine().toString() + ":" +
source.getLocation().getStartColumn()
) and
location = tainted.getLocation() and
element = tainted.toString()
)
}
}
import MakeTest<MergeTests<IRDefaultTaintTrackingTest, AstTaintTrackingTest>>

View File

@@ -1,129 +0,0 @@
#include "../shared.h"
struct S {
void(*f)(const char*);
void apply(char* p) {
f(p);
}
void (*get())(const char*) {
return f;
}
};
void calls_sink_with_argv(const char* a) { // $ ir-path=96:26 ir-path=102:26
sink(a); // $ ast=96:26 ast=98:18 ir-sink
}
extern int i;
class BaseWithPureVirtual {
public:
virtual void f(const char*) = 0;
};
class DerivedCallsSink : public BaseWithPureVirtual {
public:
void f(const char* p) override { // $ ir-path
sink(p); // $ ast=108:10 ir-sink SPURIOUS: ast=111:10
}
};
class DerivedDoesNotCallSink : public BaseWithPureVirtual {
public:
void f(const char* p) override {}
};
class DerivedCallsSinkDiamond1 : virtual public BaseWithPureVirtual {
public:
void f(const char* p) override { // $ ir-path
sink(p); // $ ast ir-sink
}
};
class DerivedDoesNotCallSinkDiamond2 : virtual public BaseWithPureVirtual {
public:
void f(const char* p) override {}
};
class DerivesMultiple : public DerivedCallsSinkDiamond1, public DerivedDoesNotCallSinkDiamond2 {
void f(const char* p) override { // $ ir-path=53:37 ir-path=115:11
DerivedCallsSinkDiamond1::f(p); // $ ir-path
}
};
template<typename T>
class CRTP {
public:
void f(const char* p) { // $ ir-path
static_cast<T*>(this)->g(p); // $ ir-path
}
};
class CRTPCallsSink : public CRTP<CRTPCallsSink> {
public:
void g(const char* p) { // $ ir-path
sink(p); // $ ast ir-sink
}
};
class Derived1 : public BaseWithPureVirtual {};
class Derived2 : public Derived1 {
public:
void f(const char* p) override {}
};
class Derived3 : public Derived2 {
public:
void f(const char* p) override { // $ ir-path=124:19 ir-path=126:43 ir-path=128:44
sink(p); // $ ast=124:19 ast=126:43 ast=128:44 ir-sink
}
};
class CRTPDoesNotCallSink : public CRTP<CRTPDoesNotCallSink> {
public:
void g(const char* p) {}
};
int main(int argc, char *argv[]) {
sink(argv[0]); // $ ast,ir-path,ir-sink
sink(reinterpret_cast<int>(argv)); // $ ast,ir-sink
calls_sink_with_argv(argv[1]); // $ ast,ir-path
char*** p = &argv; // $ ast,ir-path
sink(*p[0]); // $ ast ir-sink=96:26 ir-sink=98:18 ir-sink=98:17
calls_sink_with_argv(*p[i]); // $ ir-path=96:26 ir-path=98:18 ir-path=98:17 MISSING:ast
sink(*(argv + 1)); // $ ast ir-path ir-sink
BaseWithPureVirtual* b = new DerivedCallsSink;
b->f(argv[1]); // $ ast,ir-path
b = new DerivedDoesNotCallSink;
b->f(argv[0]); // $ SPURIOUS: ast
BaseWithPureVirtual* b2 = new DerivesMultiple;
b2->f(argv[i]); // $ ast,ir-path
CRTP<CRTPDoesNotCallSink> crtp_not_call_sink;
crtp_not_call_sink.f(argv[0]); // clean
CRTP<CRTPCallsSink> crtp_calls_sink;
crtp_calls_sink.f(argv[0]); // $ ast,ir-path
Derived1* calls_sink = new Derived3;
calls_sink->f(argv[1]); // $ ast,ir-path
static_cast<Derived2*>(calls_sink)->f(argv[1]); // $ ast,ir-path
dynamic_cast<Derived2*>(calls_sink)->f(argv[1]); // $ ast,ir-path
}

View File

@@ -1,237 +0,0 @@
#include "../shared.h"
int main() {
sink(_strdup(getenv("VAR"))); // $ ir MISSING: ast
sink(strdup(getenv("VAR"))); // $ ast,ir
sink(unmodeled_function(getenv("VAR"))); // clean by assumption
char untainted_buf[100] = "";
char buf[100] = "VAR = ";
sink(strcat(buf, getenv("VAR"))); // $ ast,ir
sink(buf); // $ ast,ir
sink(untainted_buf); // the two buffers would be conflated if we added flow through all partial chi inputs
return 0;
}
typedef unsigned int inet_addr_retval;
inet_addr_retval inet_addr(const char *dotted_address);
void sink(inet_addr_retval);
void test_indirect_arg_to_model() {
// This test is non-sensical but carefully arranged so we get data flow into
// inet_addr not through the function argument but through its associated
// read side effect.
void *env_pointer = getenv("VAR"); // env_pointer is tainted, not its data.
inet_addr_retval a = inet_addr((const char *)&env_pointer);
sink(a); // $ ast,ir
}
namespace std {
template< class T >
T&& move( T&& t ) noexcept;
}
void test_std_move() {
sink(std::move(getenv("VAR"))); // $ ir MISSING: ast
}
void flow_to_outparam(char ** ret, char *arg) {
*ret = arg;
}
void test_outparams() {
char *p2 = nullptr;
flow_to_outparam(&p2, getenv("VAR"));
sink(p2); // $ ir MISSING: ast
}
struct XY {
int x;
int y;
};
void taint_y(XY *xyp) {
int tainted = getenv("VAR")[0];
xyp->y = tainted;
}
void test_conflated_fields3() {
XY xy;
xy.x = 0;
taint_y(&xy);
sink(xy.x); // not tainted
}
struct Point {
int x;
int y;
void callSink() {
sink(this->x); // $ ir MISSING: ast
sink(this->y); // not tainted
}
};
void test_conflated_fields1() {
Point p;
p.x = getenv("VAR")[0];
sink(p.x); // $ ir MISSING: ast
sink(p.y); // not tainted
p.callSink();
}
void taint_x(Point *pp) {
pp->x = getenv("VAR")[0];
}
void y_to_sink(Point *pp) {
sink(pp->y); // not tainted
}
void test_conflated_fields2() {
Point p;
taint_x(&p);
y_to_sink(&p);
}
void sink(Point*);
void sink(Point);
void test_field_to_obj_taint_object(Point p) {
p.x = getenv("VAR")[0];
sink(p); // not tainted
sink(p.x); // $ ir MISSING: ast
}
void test_field_to_obj_taint_object_addrof(Point p) {
taint_x(&p);
sink(p); // not tainted
sink(&p); // not tainted
sink(p.x); // $ ir MISSING: ast
}
void test_field_to_obj_taint_pointer(Point* pp) {
pp->x = getenv("VAR")[0];
sink(pp);// not tainted
sink(*pp); // not tainted
}
void call_sink_on_object(Point* pp) {
sink(pp);// not tainted
sink(*pp);// not tainted
}
void test_field_to_obj_taint_call_sink(Point* pp) {
pp->x = getenv("VAR")[0];
call_sink_on_object(pp);
}
void test_field_to_obj_taint_through_setter(Point* pp) {
taint_x(pp);
sink(pp);// not tainted
sink(*pp); // not tainted
}
Point* getPoint();
void test_field_to_obj_local_variable() {
Point* pp = getPoint();
pp->x = getenv("VAR")[0];
sink(pp); // not tainted
sink(*pp); // not tainted
}
void test_field_to_obj_taint_array(Point* pp, int i) {
pp[0].x = getenv("VAR")[0];
sink(pp[i]); // not tainted
sink(pp);// not tainted
sink(*pp); // not tainted
}
void test_field_to_obj_test_pointer_arith(Point* pp) {
(pp + sizeof(*pp))->x = getenv("VAR")[0];
sink(pp);// not tainted
sink(pp + sizeof(*pp));// not tainted
}
void sink(char **);
void test_pointers1()
{
char buffer[1024];
char *s = getenv("VAR");
char *ptr1, **ptr2;
char *ptr3, **ptr4;
ptr1 = buffer;
ptr2 = &ptr1;
memcpy(buffer, s, 1024);
ptr3 = buffer;
ptr4 = &ptr3;
sink(buffer); // $ ast,ir
sink(ptr1); // $ ast MISSING: ir
sink(ptr2); // $ SPURIOUS: ast
sink(*ptr2); // $ ast MISSING: ir
sink(ptr3); // $ ast,ir
sink(ptr4); // $ SPURIOUS: ast,ir
sink(*ptr4); // $ ast,ir
}
void test_pointers2()
{
char buffer[1024];
char *s = getenv("VAR");
char *ptr1, **ptr2;
char *ptr3, **ptr4;
ptr1 = buffer;
ptr2 = &ptr1;
memcpy(*ptr2, s, 1024);
ptr3 = buffer;
ptr4 = &ptr3;
sink(buffer); // $ MISSING: ast,ir
sink(ptr1); // $ ast MISSING: ir
sink(ptr2); // $ SPURIOUS: ast,ir
sink(*ptr2); // $ ast,ir
sink(ptr3); // $ MISSING: ast,ir
sink(ptr4); // clean
sink(*ptr4); // $ MISSING: ast,ir
}
// --- recv ---
int recv(int s, char* buf, int len, int flags);
void test_recv() {
char buffer[1024];
recv(0, buffer, sizeof(buffer), 0);
sink(buffer); // $ ast,ir
sink(*buffer); // $ ast,ir
}
// --- send and related functions ---
struct iovec {
void *iov_base;
unsigned iov_len;
};
int readv(int, const struct iovec*, int);
void sink(const iovec* iovs);
void sink(iovec);
void test_readv_and_writev(iovec* iovs) {
readv(0, iovs, 16);
sink(iovs); // $ast,ir
sink(iovs[0]); // $ast,ir
sink(*iovs); // $ast,ir
char* p = (char*)iovs[1].iov_base;
sink(p); // $ MISSING: ast,ir
sink(*p); // $ MISSING: ast,ir
}

View File

@@ -1,159 +0,0 @@
#include "../shared.h"
typedef unsigned long size_t;
namespace std
{
template<class charT> struct char_traits;
typedef size_t streamsize;
template <class T> class allocator {
public:
allocator() throw();
};
template<class charT, class traits = char_traits<charT>, class Allocator = allocator<charT> >
class basic_string {
public:
explicit basic_string(const Allocator& a = Allocator());
basic_string(const charT* s, const Allocator& a = Allocator());
const charT* c_str() const;
};
typedef basic_string<char> string;
template <class charT, class traits = char_traits<charT> >
class basic_istream /*: virtual public basic_ios<charT,traits> - not needed for this test */ {
public:
basic_istream<charT,traits>& operator>>(int& n);
};
template <class charT, class traits = char_traits<charT> >
class basic_ostream /*: virtual public basic_ios<charT,traits> - not needed for this test */ {
public:
typedef charT char_type;
basic_ostream<charT,traits>& write(const char_type* s, streamsize n);
basic_ostream<charT, traits>& operator<<(int n);
};
template<class charT, class traits> basic_ostream<charT,traits>& operator<<(basic_ostream<charT,traits>&, const charT*);
template<class charT, class traits, class Allocator> basic_ostream<charT, traits>& operator<<(basic_ostream<charT, traits>& os, const basic_string<charT, traits, Allocator>& str);
template<class charT, class traits = char_traits<charT>>
class basic_iostream : public basic_istream<charT, traits>, public basic_ostream<charT, traits> {
public:
};
template<class charT, class traits = char_traits<charT>, class Allocator = allocator<charT>>
class basic_stringstream : public basic_iostream<charT, traits> {
public:
explicit basic_stringstream(/*ios_base::openmode which = ios_base::out|ios_base::in - not needed for this test*/);
basic_string<charT, traits, Allocator> str() const;
};
using stringstream = basic_stringstream<char>;
}
char *source() { return getenv("USERDATA"); }
void sink(const std::string &s) {};
void sink(const std::stringstream &s) {};
void test_string()
{
char *a = source();
std::string b("123");
std::string c(source());
sink(a); // $ ast,ir
sink(b); // clean
sink(c); // $ ir MISSING: ast
sink(b.c_str()); // clean
sink(c.c_str()); // $ ir MISSING: ast
}
void test_stringstream()
{
std::stringstream ss1, ss2, ss3, ss4, ss5;
std::string t(source());
ss1 << "1234";
ss2 << source();
ss3 << "123" << source();
ss4 << source() << "456";
ss5 << t;
sink(ss1);
sink(ss2); // $ ir MISSING: ast
sink(ss3); // $ ir MISSING: ast
sink(ss4); // $ ir MISSING: ast
sink(ss5); // $ ir MISSING: ast
sink(ss1.str());
sink(ss2.str()); // $ ir MISSING: ast
sink(ss3.str()); // $ ir MISSING: ast
sink(ss4.str()); // $ ir MISSING: ast
sink(ss5.str()); // $ ir MISSING: ast
}
void test_stringstream_int(int source)
{
std::stringstream ss1, ss2;
ss1 << 1234;
ss2 << source;
sink(ss1); // clean
sink(ss2); // $ MISSING: ast,ir
sink(ss1.str()); // clean
sink(ss2.str()); // $ MISSING: ast,ir
}
using namespace std;
char *user_input() {
return source();
}
void sink(const char *filename, const char *mode);
void test_strings2()
{
string path1 = user_input();
sink(path1.c_str(), "r"); // $ ir MISSING: ast
string path2;
path2 = user_input();
sink(path2.c_str(), "r"); // $ ir MISSING: ast
string path3(user_input());
sink(path3.c_str(), "r"); // $ ir MISSING: ast
}
void test_string3()
{
const char *cs = source();
// convert char * -> std::string
std::string ss(cs);
sink(cs); // $ ast,ir
sink(ss); // $ ir MISSING: ast
}
void test_string4()
{
const char *cs = source();
// convert char * -> std::string
std::string ss(cs);
// convert back std::string -> char *
cs = ss.c_str();
sink(cs); // $ ast,ir
sink(ss); // $ ir MISSING: ast
}

View File

@@ -1,4 +0,0 @@
WARNING: Module TaintedWithPath has been deprecated and may be removed in future (tainted.ql:10,8-47)
WARNING: Predicate tainted has been deprecated and may be removed in future (tainted.ql:21,3-28)
testFailures
failures

View File

@@ -1,80 +0,0 @@
/**
* This test provides the usual facilities to annotate taint flow when reaching a sink.
* This is different when compared to the tests in `../annotate_path_to_sink`, where all elements on a taint path to a sink
* are annotated.
*/
import cpp
import semmle.code.cpp.security.TaintTrackingImpl as AstTaintTracking
import semmle.code.cpp.ir.dataflow.DefaultTaintTracking as IRDefaultTaintTracking
import IRDefaultTaintTracking::TaintedWithPath as TaintedWithPath
import TestUtilities.InlineExpectationsTest
predicate argToSinkCall(Element sink) {
exists(FunctionCall call |
call.getTarget().getName() = "sink" and
sink = call.getAnArgument()
)
}
predicate astTaint(Expr source, Element sink) {
AstTaintTracking::tainted(source, sink) and argToSinkCall(sink)
}
class SourceConfiguration extends TaintedWithPath::TaintTrackingConfiguration {
override predicate isSink(Element e) { argToSinkCall(e) }
}
predicate irTaint(Expr source, Element sink) {
TaintedWithPath::taintedWithPath(source, sink, _, _)
}
module IRDefaultTaintTrackingTest implements TestSig {
string getARelevantTag() { result = "ir" }
predicate hasActualResult(Location location, string element, string tag, string value) {
exists(Expr source, Element tainted, int n |
tag = "ir" and
irTaint(source, tainted) and
n = strictcount(Expr otherSource | irTaint(otherSource, tainted)) and
(
n = 1 and value = ""
or
// If there is more than one source for this sink
// we specify the source location explicitly.
n > 1 and
value =
source.getLocation().getStartLine().toString() + ":" +
source.getLocation().getStartColumn()
) and
location = tainted.getLocation() and
element = tainted.toString()
)
}
}
module AstTaintTrackingTest implements TestSig {
string getARelevantTag() { result = "ast" }
predicate hasActualResult(Location location, string element, string tag, string value) {
exists(Expr source, Element tainted, int n |
tag = "ast" and
astTaint(source, tainted) and
n = strictcount(Expr otherSource | astTaint(otherSource, tainted)) and
(
n = 1 and value = ""
or
// If there is more than one source for this sink
// we specify the source location explicitly.
n > 1 and
value =
source.getLocation().getStartLine().toString() + ":" +
source.getLocation().getStartColumn()
) and
location = tainted.getLocation() and
element = tainted.toString()
)
}
}
import MakeTest<MergeTests<IRDefaultTaintTrackingTest, AstTaintTrackingTest>>

View File

@@ -1,4 +0,0 @@
WARNING: Predicate taintedIncludingGlobalVars has been deprecated and may be removed in future (global.ql:8,3-47)
WARNING: Predicate taintedIncludingGlobalVars has been deprecated and may be removed in future (global.ql:12,3-53)
failures
testFailures

View File

@@ -1,41 +0,0 @@
import cpp
import semmle.code.cpp.security.Security
import semmle.code.cpp.security.TaintTrackingImpl as AstTaintTracking
import semmle.code.cpp.ir.dataflow.DefaultTaintTracking as IRDefaultTaintTracking
import TestUtilities.InlineExpectationsTest
predicate astTaint(Expr source, Element sink, string globalVar) {
AstTaintTracking::taintedIncludingGlobalVars(source, sink, globalVar) and globalVar != ""
}
predicate irTaint(Expr source, Element sink, string globalVar) {
IRDefaultTaintTracking::taintedIncludingGlobalVars(source, sink, globalVar) and globalVar != ""
}
module IRGlobalDefaultTaintTrackingTest implements TestSig {
string getARelevantTag() { result = "ir" }
predicate hasActualResult(Location location, string element, string tag, string value) {
exists(Element tainted |
tag = "ir" and
irTaint(_, tainted, value) and
location = tainted.getLocation() and
element = tainted.toString()
)
}
}
module AstGlobalDefaultTaintTrackingTest implements TestSig {
string getARelevantTag() { result = "ast" }
predicate hasActualResult(Location location, string element, string tag, string value) {
exists(Element tainted |
tag = "ast" and
astTaint(_, tainted, value) and
location = tainted.getLocation() and
element = tainted.toString()
)
}
}
import MakeTest<MergeTests<IRGlobalDefaultTaintTrackingTest, AstGlobalDefaultTaintTrackingTest>>

View File

@@ -1,24 +0,0 @@
char *getenv(const char *name);
void sink(const char *sinkparam); // $ ast,ir=global1 ast,ir=global2
void throughLocal() {
char * local = getenv("VAR");
sink(local);
}
char * global1 = 0;
void readWriteGlobal1() {
sink(global1); // $ ast,ir=global1
global1 = getenv("VAR");
}
static char * global2 = 0;
void readGlobal2() {
sink(global2); // $ ast,ir=global2
}
void writeGlobal2() {
global2 = getenv("VAR");
}

View File

@@ -1,17 +0,0 @@
// Common declarations in this test dir should go in this file. Otherwise, some
// declarations will have multiple locations, which leads to confusing test
// output.
void sink(const char *sinkparam);
void sink(int sinkparam);
int atoi(const char *nptr);
char *getenv(const char *name);
char *strcat(char * s1, const char * s2);
char *strdup(const char *string);
char *_strdup(const char *string);
char *unmodeled_function(const char *const_string);
typedef unsigned long size_t;
void *memcpy(void *s1, const void *s2, size_t n);

View File

@@ -1,91 +0,0 @@
WARNING: Predicate taintedIncludingGlobalVars has been deprecated and may be removed in future (tainted.ql:5,3-29)
| test.cpp:23:23:23:28 | call to getenv | test.cpp:8:24:8:25 | s1 | |
| test.cpp:23:23:23:28 | call to getenv | test.cpp:23:14:23:19 | envStr | |
| test.cpp:23:23:23:28 | call to getenv | test.cpp:23:23:23:28 | call to getenv | |
| test.cpp:23:23:23:28 | call to getenv | test.cpp:23:23:23:40 | (const char *)... | |
| test.cpp:23:23:23:28 | call to getenv | test.cpp:25:6:25:29 | ! ... | |
| test.cpp:23:23:23:28 | call to getenv | test.cpp:25:7:25:12 | call to strcmp | |
| test.cpp:23:23:23:28 | call to getenv | test.cpp:25:7:25:29 | (bool)... | |
| test.cpp:23:23:23:28 | call to getenv | test.cpp:25:14:25:19 | envStr | |
| test.cpp:23:23:23:28 | call to getenv | test.cpp:29:6:29:28 | ! ... | |
| test.cpp:23:23:23:28 | call to getenv | test.cpp:29:7:29:12 | call to strcmp | |
| test.cpp:23:23:23:28 | call to getenv | test.cpp:29:7:29:28 | (bool)... | |
| test.cpp:23:23:23:28 | call to getenv | test.cpp:29:14:29:19 | envStr | |
| test.cpp:38:23:38:28 | call to getenv | test.cpp:8:24:8:25 | s1 | |
| test.cpp:38:23:38:28 | call to getenv | test.cpp:38:14:38:19 | envStr | |
| test.cpp:38:23:38:28 | call to getenv | test.cpp:38:23:38:28 | call to getenv | |
| test.cpp:38:23:38:28 | call to getenv | test.cpp:38:23:38:40 | (const char *)... | |
| test.cpp:38:23:38:28 | call to getenv | test.cpp:40:14:40:19 | envStr | |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:8:24:8:25 | s1 | envStrGlobal |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:45:13:45:24 | envStrGlobal | |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:45:13:45:24 | envStrGlobal | envStrGlobal |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:49:14:49:19 | envStr | |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:49:23:49:28 | call to getenv | |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:49:23:49:40 | (const char *)... | |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:50:15:50:24 | envStr_ptr | |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:50:15:50:24 | envStr_ptr | envStrGlobal |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:50:28:50:40 | & ... | envStrGlobal |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:50:29:50:40 | envStrGlobal | envStrGlobal |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:52:2:52:12 | * ... | |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:52:3:52:12 | envStr_ptr | |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:52:16:52:21 | envStr | |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:6:54:35 | ! ... | envStrGlobal |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:7:54:12 | call to strcmp | envStrGlobal |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:7:54:35 | (bool)... | envStrGlobal |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:14:54:25 | envStrGlobal | envStrGlobal |
| test.cpp:60:29:60:34 | call to getenv | test.cpp:10:27:10:27 | s | |
| test.cpp:60:29:60:34 | call to getenv | test.cpp:60:18:60:25 | userName | |
| test.cpp:60:29:60:34 | call to getenv | test.cpp:60:29:60:34 | call to getenv | |
| test.cpp:60:29:60:34 | call to getenv | test.cpp:60:29:60:47 | (const char *)... | |
| test.cpp:60:29:60:34 | call to getenv | test.cpp:64:25:64:32 | userName | |
| test.cpp:68:28:68:33 | call to getenv | test.cpp:11:20:11:21 | s1 | |
| test.cpp:68:28:68:33 | call to getenv | test.cpp:11:36:11:37 | s2 | |
| test.cpp:68:28:68:33 | call to getenv | test.cpp:67:7:67:13 | copying | |
| test.cpp:68:28:68:33 | call to getenv | test.cpp:68:17:68:24 | userName | |
| test.cpp:68:28:68:33 | call to getenv | test.cpp:68:28:68:33 | call to getenv | |
| test.cpp:68:28:68:33 | call to getenv | test.cpp:68:28:68:46 | (const char *)... | |
| test.cpp:68:28:68:33 | call to getenv | test.cpp:69:10:69:13 | copy | |
| test.cpp:68:28:68:33 | call to getenv | test.cpp:70:5:70:10 | call to strcpy | |
| test.cpp:68:28:68:33 | call to getenv | test.cpp:70:12:70:15 | copy | |
| test.cpp:68:28:68:33 | call to getenv | test.cpp:70:18:70:25 | userName | |
| test.cpp:68:28:68:33 | call to getenv | test.cpp:71:12:71:15 | copy | |
| test.cpp:75:20:75:25 | call to getenv | test.cpp:15:22:15:25 | nptr | |
| test.cpp:75:20:75:25 | call to getenv | test.cpp:75:15:75:18 | call to atoi | |
| test.cpp:75:20:75:25 | call to getenv | test.cpp:75:20:75:25 | call to getenv | |
| test.cpp:75:20:75:25 | call to getenv | test.cpp:75:20:75:45 | (const char *)... | |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:8:24:8:25 | s1 | |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:11:20:11:21 | s1 | |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:11:36:11:37 | s2 | |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:83:17:83:24 | userName | |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:83:28:83:33 | call to getenv | |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:83:28:83:46 | (const char *)... | |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:85:8:85:11 | copy | |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:86:2:86:7 | call to strcpy | |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:86:9:86:12 | copy | |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:86:15:86:22 | userName | |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:6:88:27 | ! ... | |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:7:88:12 | call to strcmp | |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:7:88:27 | (bool)... | |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:14:88:17 | (const char *)... | |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:14:88:17 | copy | |
| test.cpp:100:12:100:15 | call to gets | test.cpp:98:8:98:14 | pointer | |
| test.cpp:100:12:100:15 | call to gets | test.cpp:100:2:100:8 | pointer | |
| test.cpp:100:12:100:15 | call to gets | test.cpp:100:12:100:15 | call to gets | |
| test.cpp:100:17:100:22 | buffer | test.cpp:93:18:93:18 | s | |
| test.cpp:100:17:100:22 | buffer | test.cpp:97:7:97:12 | buffer | |
| test.cpp:100:17:100:22 | buffer | test.cpp:100:17:100:22 | buffer | |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:8:24:8:25 | s1 | |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:11:20:11:21 | s1 | |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:11:36:11:37 | s2 | |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:106:17:106:24 | userName | |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:106:28:106:33 | call to getenv | |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:106:28:106:46 | (const char *)... | |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:108:8:108:11 | copy | |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:109:2:109:7 | call to strcpy | |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:109:9:109:12 | copy | |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:109:15:109:22 | userName | |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:111:6:111:27 | ! ... | |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:111:7:111:12 | call to strcmp | |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:111:7:111:27 | (bool)... | |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:111:14:111:17 | (const char *)... | |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:111:14:111:17 | copy | |

View File

@@ -1,7 +0,0 @@
import semmle.code.cpp.security.TaintTrackingImpl
from Expr source, Element tainted, string globalVar
where
taintedIncludingGlobalVars(source, tainted, globalVar) and
not tainted.getLocation().getFile().getExtension() = "h"
select source, tainted, globalVar

View File

@@ -1,51 +0,0 @@
WARNING: Module TaintedWithPath has been deprecated and may be removed in future (tainted_diff.ql:5,35-54)
WARNING: Module TaintedWithPath has been deprecated and may be removed in future (tainted_diff.ql:12,7-26)
WARNING: Module TaintedWithPath has been deprecated and may be removed in future (tainted_diff.ql:16,3-22)
WARNING: Predicate taintedIncludingGlobalVars has been deprecated and may be removed in future (tainted_diff.ql:11,3-34)
WARNING: Predicate taintedIncludingGlobalVars has been deprecated and may be removed in future (tainted_diff.ql:17,7-38)
| test.cpp:23:23:23:28 | call to getenv | test.cpp:8:24:8:25 | s1 | AST only |
| test.cpp:23:23:23:28 | call to getenv | test.cpp:23:14:23:19 | envStr | AST only |
| test.cpp:38:23:38:28 | call to getenv | test.cpp:8:24:8:25 | s1 | AST only |
| test.cpp:38:23:38:28 | call to getenv | test.cpp:38:14:38:19 | envStr | AST only |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:8:24:8:25 | s1 | AST only |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:45:13:45:24 | envStrGlobal | AST only |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:49:14:49:19 | envStr | AST only |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:50:15:50:24 | envStr_ptr | AST only |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:50:28:50:40 | & ... | AST only |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:50:29:50:40 | envStrGlobal | AST only |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:52:2:52:12 | * ... | AST only |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:52:3:52:12 | envStr_ptr | AST only |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:6:54:35 | ! ... | AST only |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:7:54:12 | call to strcmp | AST only |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:7:54:35 | (bool)... | AST only |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:14:54:25 | envStrGlobal | AST only |
| test.cpp:60:29:60:34 | call to getenv | test.cpp:10:27:10:27 | s | AST only |
| test.cpp:60:29:60:34 | call to getenv | test.cpp:60:18:60:25 | userName | AST only |
| test.cpp:68:28:68:33 | call to getenv | test.cpp:11:20:11:21 | s1 | AST only |
| test.cpp:68:28:68:33 | call to getenv | test.cpp:11:36:11:37 | s2 | AST only |
| test.cpp:68:28:68:33 | call to getenv | test.cpp:67:7:67:13 | copying | AST only |
| test.cpp:68:28:68:33 | call to getenv | test.cpp:68:17:68:24 | userName | AST only |
| test.cpp:68:28:68:33 | call to getenv | test.cpp:69:10:69:13 | copy | AST only |
| test.cpp:68:28:68:33 | call to getenv | test.cpp:70:5:70:10 | call to strcpy | AST only |
| test.cpp:68:28:68:33 | call to getenv | test.cpp:70:12:70:15 | copy | AST only |
| test.cpp:68:28:68:33 | call to getenv | test.cpp:71:12:71:15 | array to pointer conversion | IR only |
| test.cpp:75:20:75:25 | call to getenv | test.cpp:15:22:15:25 | nptr | AST only |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:8:24:8:25 | s1 | AST only |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:11:20:11:21 | s1 | AST only |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:11:36:11:37 | s2 | AST only |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:83:17:83:24 | userName | AST only |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:85:8:85:11 | copy | AST only |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:86:2:86:7 | call to strcpy | AST only |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:86:9:86:12 | copy | AST only |
| test.cpp:100:12:100:15 | call to gets | test.cpp:98:8:98:14 | pointer | AST only |
| test.cpp:100:12:100:15 | call to gets | test.cpp:100:2:100:8 | pointer | AST only |
| test.cpp:100:17:100:22 | buffer | test.cpp:93:18:93:18 | s | AST only |
| test.cpp:100:17:100:22 | buffer | test.cpp:97:7:97:12 | buffer | AST only |
| test.cpp:100:17:100:22 | buffer | test.cpp:100:17:100:22 | array to pointer conversion | IR only |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:8:24:8:25 | s1 | AST only |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:11:20:11:21 | s1 | AST only |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:11:36:11:37 | s2 | AST only |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:106:17:106:24 | userName | AST only |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:108:8:108:11 | copy | AST only |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:109:2:109:7 | call to strcpy | AST only |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:109:9:109:12 | copy | AST only |

View File

@@ -1,20 +0,0 @@
import semmle.code.cpp.security.TaintTrackingImpl as AST
import semmle.code.cpp.ir.dataflow.DefaultTaintTracking as IR
import cpp
class SourceConfiguration extends IR::TaintedWithPath::TaintTrackingConfiguration {
override predicate isSink(Element e) { any() }
}
from Expr source, Element tainted, string side
where
AST::taintedIncludingGlobalVars(source, tainted, _) and
not IR::TaintedWithPath::taintedWithPath(source, tainted, _, _) and
not tainted.getLocation().getFile().getExtension() = "h" and
side = "AST only"
or
IR::TaintedWithPath::taintedWithPath(source, tainted, _, _) and
not AST::taintedIncludingGlobalVars(source, tainted, _) and
not tainted.getLocation().getFile().getExtension() = "h" and
side = "IR only"
select source, tainted, side

View File

@@ -1,48 +0,0 @@
WARNING: Module TaintedWithPath has been deprecated and may be removed in future (tainted_ir.ql:3,35-50)
WARNING: Module TaintedWithPath has been deprecated and may be removed in future (tainted_ir.ql:9,3-18)
| test.cpp:23:23:23:28 | call to getenv | test.cpp:23:23:23:28 | call to getenv |
| test.cpp:23:23:23:28 | call to getenv | test.cpp:23:23:23:40 | (const char *)... |
| test.cpp:23:23:23:28 | call to getenv | test.cpp:25:6:25:29 | ! ... |
| test.cpp:23:23:23:28 | call to getenv | test.cpp:25:7:25:12 | call to strcmp |
| test.cpp:23:23:23:28 | call to getenv | test.cpp:25:7:25:29 | (bool)... |
| test.cpp:23:23:23:28 | call to getenv | test.cpp:25:14:25:19 | envStr |
| test.cpp:23:23:23:28 | call to getenv | test.cpp:29:6:29:28 | ! ... |
| test.cpp:23:23:23:28 | call to getenv | test.cpp:29:7:29:12 | call to strcmp |
| test.cpp:23:23:23:28 | call to getenv | test.cpp:29:7:29:28 | (bool)... |
| test.cpp:23:23:23:28 | call to getenv | test.cpp:29:14:29:19 | envStr |
| test.cpp:38:23:38:28 | call to getenv | test.cpp:38:23:38:28 | call to getenv |
| test.cpp:38:23:38:28 | call to getenv | test.cpp:38:23:38:40 | (const char *)... |
| test.cpp:38:23:38:28 | call to getenv | test.cpp:40:14:40:19 | envStr |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:49:23:49:28 | call to getenv |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:49:23:49:40 | (const char *)... |
| test.cpp:49:23:49:28 | call to getenv | test.cpp:52:16:52:21 | envStr |
| test.cpp:60:29:60:34 | call to getenv | test.cpp:60:29:60:34 | call to getenv |
| test.cpp:60:29:60:34 | call to getenv | test.cpp:60:29:60:47 | (const char *)... |
| test.cpp:60:29:60:34 | call to getenv | test.cpp:64:25:64:32 | userName |
| test.cpp:68:28:68:33 | call to getenv | test.cpp:68:28:68:33 | call to getenv |
| test.cpp:68:28:68:33 | call to getenv | test.cpp:68:28:68:46 | (const char *)... |
| test.cpp:68:28:68:33 | call to getenv | test.cpp:70:18:70:25 | userName |
| test.cpp:68:28:68:33 | call to getenv | test.cpp:71:12:71:15 | array to pointer conversion |
| test.cpp:68:28:68:33 | call to getenv | test.cpp:71:12:71:15 | copy |
| test.cpp:75:20:75:25 | call to getenv | test.cpp:75:15:75:18 | call to atoi |
| test.cpp:75:20:75:25 | call to getenv | test.cpp:75:20:75:25 | call to getenv |
| test.cpp:75:20:75:25 | call to getenv | test.cpp:75:20:75:45 | (const char *)... |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:83:28:83:33 | call to getenv |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:83:28:83:46 | (const char *)... |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:86:15:86:22 | userName |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:6:88:27 | ! ... |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:7:88:12 | call to strcmp |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:7:88:27 | (bool)... |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:14:88:17 | (const char *)... |
| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:14:88:17 | copy |
| test.cpp:100:12:100:15 | call to gets | test.cpp:100:12:100:15 | call to gets |
| test.cpp:100:17:100:22 | buffer | test.cpp:100:17:100:22 | array to pointer conversion |
| test.cpp:100:17:100:22 | buffer | test.cpp:100:17:100:22 | buffer |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:106:28:106:33 | call to getenv |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:106:28:106:46 | (const char *)... |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:109:15:109:22 | userName |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:111:6:111:27 | ! ... |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:111:7:111:12 | call to strcmp |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:111:7:111:27 | (bool)... |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:111:14:111:17 | (const char *)... |
| test.cpp:106:28:106:33 | call to getenv | test.cpp:111:14:111:17 | copy |

View File

@@ -1,11 +0,0 @@
import semmle.code.cpp.ir.dataflow.DefaultTaintTracking
class SourceConfiguration extends TaintedWithPath::TaintTrackingConfiguration {
override predicate isSink(Element e) { any() }
}
from Expr source, Element tainted
where
TaintedWithPath::taintedWithPath(source, tainted, _, _) and
not tainted.getLocation().getFile().getExtension() = "h"
select source, tainted

View File

@@ -1,114 +0,0 @@
// Test for the general-purpose taint-tracking
// mechanism that is used by several of the security queries.
///// Library functions //////
typedef unsigned long size_t;
int strcmp(const char *s1, const char *s2);
char *getenv(const char *name);
size_t strlen(const char *s);
char *strcpy(char *s1, const char *s2);
void *malloc(size_t size);
int atoi(const char *nptr);
//// Test code /////
bool isAdmin = false;
void test1()
{
const char *envStr = getenv("USERINFO");
if (!strcmp(envStr, "admin")) {
isAdmin = true;
}
if (!strcmp(envStr, "none")) {
isAdmin = false;
}
}
extern const char *specialUser;
void test2()
{
const char *envStr = getenv("USERINFO");
if (!strcmp(envStr, specialUser)) {
isAdmin = true;
}
}
const char *envStrGlobal;
void test3()
{
const char *envStr = getenv("USERINFO");
const char **envStr_ptr = &envStrGlobal;
*envStr_ptr = envStr;
if (!strcmp(envStrGlobal, "admin")) {
isAdmin = true;
}
}
void bugWithBinop() {
const char *userName = getenv("USER_NAME");
// The following is tainted, but should not cause
// the whole program to be considered tainted.
int bytes = strlen(userName) + 1;
}
char* copying() {
const char *userName = getenv("USER_NAME");
char copy[1024];
strcpy(copy, userName);
return copy; // copy should be tainted
}
void guard() {
int len = atoi(getenv("FOOBAZ_BRANCHING"));
if (len > 1000) return;
char **node = (char **) malloc(len * sizeof(char *));
}
const char *alias_global;
void mallocBuffer() {
const char *userName = getenv("USER_NAME");
char *alias = (char*)malloc(4096);
char *copy = (char*)malloc(4096);
strcpy(copy, userName);
alias_global = alias; // to force a Chi node on all aliased memory
if (!strcmp(copy, "admin")) { // copy should be tainted
isAdmin = true;
}
}
char *gets(char *s);
void test_gets()
{
char buffer[1024];
char *pointer;
pointer = gets(buffer);
}
const char *alias_global_new;
void newBuffer() {
const char *userName = getenv("USER_NAME");
char *alias = new char[4096];
char *copy = new char[4096];
strcpy(copy, userName);
alias_global_new = alias; // to force a Chi node on all aliased memory
if (!strcmp(copy, "admin")) { // copy should be tainted
isAdmin = true;
}
}