mirror of
https://github.com/github/codeql.git
synced 2026-07-21 03:08:25 +02:00
3007 lines
93 KiB
Plaintext
3007 lines
93 KiB
Plaintext
/**
|
|
* Provides an implementation of global (interprocedural) data flow. This file
|
|
* re-exports the local (intraprocedural) data flow analysis from
|
|
* `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed
|
|
* through the `Configuration` class. This file exists in several identical
|
|
* copies, allowing queries to use multiple `Configuration` classes that depend
|
|
* on each other without introducing mutual recursion among those configurations.
|
|
*/
|
|
|
|
private import DataFlowImplCommon
|
|
private import DataFlowImplSpecific::Private
|
|
import DataFlowImplSpecific::Public
|
|
|
|
/**
|
|
* A configuration of interprocedural data flow analysis. This defines
|
|
* sources, sinks, and any other configurable aspect of the analysis. Each
|
|
* use of the global data flow library must define its own unique extension
|
|
* of this abstract class. To create a configuration, extend this class with
|
|
* a subclass whose characteristic predicate is a unique singleton string.
|
|
* For example, write
|
|
*
|
|
* ```ql
|
|
* class MyAnalysisConfiguration extends DataFlow::Configuration {
|
|
* MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" }
|
|
* // Override `isSource` and `isSink`.
|
|
* // Optionally override `isBarrier`.
|
|
* // Optionally override `isAdditionalFlowStep`.
|
|
* }
|
|
* ```
|
|
* Conceptually, this defines a graph where the nodes are `DataFlow::Node`s and
|
|
* the edges are those data-flow steps that preserve the value of the node
|
|
* along with any additional edges defined by `isAdditionalFlowStep`.
|
|
* Specifying nodes in `isBarrier` will remove those nodes from the graph, and
|
|
* specifying nodes in `isBarrierIn` and/or `isBarrierOut` will remove in-going
|
|
* and/or out-going edges from those nodes, respectively.
|
|
*
|
|
* Then, to query whether there is flow between some `source` and `sink`,
|
|
* write
|
|
*
|
|
* ```ql
|
|
* exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink))
|
|
* ```
|
|
*
|
|
* Multiple configurations can coexist, but two classes extending
|
|
* `DataFlow::Configuration` should never depend on each other. One of them
|
|
* should instead depend on a `DataFlow2::Configuration`, a
|
|
* `DataFlow3::Configuration`, or a `DataFlow4::Configuration`.
|
|
*/
|
|
abstract class Configuration extends string {
|
|
bindingset[this]
|
|
Configuration() { any() }
|
|
|
|
/**
|
|
* Holds if `source` is a relevant data flow source.
|
|
*/
|
|
abstract predicate isSource(Node source);
|
|
|
|
/**
|
|
* Holds if `sink` is a relevant data flow sink.
|
|
*/
|
|
abstract predicate isSink(Node sink);
|
|
|
|
/**
|
|
* Holds if data flow through `node` is prohibited. This completely removes
|
|
* `node` from the data flow graph.
|
|
*/
|
|
predicate isBarrier(Node node) { none() }
|
|
|
|
/** Holds if data flow into `node` is prohibited. */
|
|
predicate isBarrierIn(Node node) { none() }
|
|
|
|
/** Holds if data flow out of `node` is prohibited. */
|
|
predicate isBarrierOut(Node node) { none() }
|
|
|
|
/** Holds if data flow through nodes guarded by `guard` is prohibited. */
|
|
predicate isBarrierGuard(BarrierGuard guard) { none() }
|
|
|
|
/**
|
|
* Holds if the additional flow step from `node1` to `node2` must be taken
|
|
* into account in the analysis.
|
|
*/
|
|
predicate isAdditionalFlowStep(Node node1, Node node2) { none() }
|
|
|
|
/**
|
|
* Gets the virtual dispatch branching limit when calculating field flow.
|
|
* This can be overridden to a smaller value to improve performance (a
|
|
* value of 0 disables field flow), or a larger value to get more results.
|
|
*/
|
|
int fieldFlowBranchLimit() { result = 2 }
|
|
|
|
/**
|
|
* Holds if data may flow from `source` to `sink` for this configuration.
|
|
*/
|
|
predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) }
|
|
|
|
/**
|
|
* Holds if data may flow from `source` to `sink` for this configuration.
|
|
*
|
|
* The corresponding paths are generated from the end-points and the graph
|
|
* included in the module `PathGraph`.
|
|
*/
|
|
predicate hasFlowPath(PathNode source, PathNode sink) { flowsTo(source, sink, _, _, this) }
|
|
|
|
/**
|
|
* Holds if data may flow from some source to `sink` for this configuration.
|
|
*/
|
|
predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
|
|
|
|
/**
|
|
* Holds if data may flow from some source to `sink` for this configuration.
|
|
*/
|
|
predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
|
|
|
|
/**
|
|
* Gets the exploration limit for `hasPartialFlow` measured in approximate
|
|
* number of interprocedural steps.
|
|
*/
|
|
int explorationLimit() { none() }
|
|
|
|
/**
|
|
* Holds if there is a partial data flow path from `source` to `node`. The
|
|
* approximate distance between `node` and the closest source is `dist` and
|
|
* is restricted to be less than or equal to `explorationLimit()`. This
|
|
* predicate completely disregards sink definitions.
|
|
*
|
|
* This predicate is intended for dataflow exploration and debugging and may
|
|
* perform poorly if the number of sources is too big and/or the exploration
|
|
* limit is set too high without using barriers.
|
|
*
|
|
* This predicate is disabled (has no results) by default. Override
|
|
* `explorationLimit()` with a suitable number to enable this predicate.
|
|
*
|
|
* To use this in a `path-problem` query, import the module `PartialPathGraph`.
|
|
*/
|
|
final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) {
|
|
partialFlow(source, node, this) and
|
|
dist = node.getSourceDistance()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This class exists to prevent mutual recursion between the user-overridden
|
|
* member predicates of `Configuration` and the rest of the data-flow library.
|
|
* Good performance cannot be guaranteed in the presence of such recursion, so
|
|
* it should be replaced by using more than one copy of the data flow library.
|
|
*/
|
|
abstract private class ConfigurationRecursionPrevention extends Configuration {
|
|
bindingset[this]
|
|
ConfigurationRecursionPrevention() { any() }
|
|
|
|
override predicate hasFlow(Node source, Node sink) {
|
|
strictcount(Node n | this.isSource(n)) < 0
|
|
or
|
|
strictcount(Node n | this.isSink(n)) < 0
|
|
or
|
|
strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0
|
|
or
|
|
super.hasFlow(source, sink)
|
|
}
|
|
}
|
|
|
|
private predicate inBarrier(Node node, Configuration config) {
|
|
config.isBarrierIn(node) and
|
|
config.isSource(node)
|
|
}
|
|
|
|
private predicate outBarrier(Node node, Configuration config) {
|
|
config.isBarrierOut(node) and
|
|
config.isSink(node)
|
|
}
|
|
|
|
private predicate fullBarrier(Node node, Configuration config) {
|
|
config.isBarrier(node)
|
|
or
|
|
config.isBarrierIn(node) and
|
|
not config.isSource(node)
|
|
or
|
|
config.isBarrierOut(node) and
|
|
not config.isSink(node)
|
|
or
|
|
exists(BarrierGuard g |
|
|
config.isBarrierGuard(g) and
|
|
node = g.getAGuardedNode()
|
|
)
|
|
}
|
|
|
|
private class AdditionalFlowStepSource extends Node {
|
|
AdditionalFlowStepSource() { any(Configuration c).isAdditionalFlowStep(this, _) }
|
|
}
|
|
|
|
pragma[noinline]
|
|
private predicate isAdditionalFlowStep(
|
|
AdditionalFlowStepSource node1, Node node2, DataFlowCallable callable1, Configuration config
|
|
) {
|
|
config.isAdditionalFlowStep(node1, node2) and
|
|
callable1 = node1.getEnclosingCallable()
|
|
}
|
|
|
|
/**
|
|
* Holds if data can flow in one local step from `node1` to `node2`.
|
|
*/
|
|
private predicate localFlowStep(Node node1, Node node2, Configuration config) {
|
|
simpleLocalFlowStep(node1, node2) and
|
|
not outBarrier(node1, config) and
|
|
not inBarrier(node2, config) and
|
|
not fullBarrier(node1, config) and
|
|
not fullBarrier(node2, config)
|
|
}
|
|
|
|
/**
|
|
* Holds if the additional step from `node1` to `node2` does not jump between callables.
|
|
*/
|
|
private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration config) {
|
|
isAdditionalFlowStep(node1, node2, node2.getEnclosingCallable(), config) and
|
|
not outBarrier(node1, config) and
|
|
not inBarrier(node2, config) and
|
|
not fullBarrier(node1, config) and
|
|
not fullBarrier(node2, config)
|
|
}
|
|
|
|
/**
|
|
* Holds if data can flow from `node1` to `node2` in a way that discards call contexts.
|
|
*/
|
|
private predicate jumpStep(Node node1, Node node2, Configuration config) {
|
|
jumpStep(node1, node2) and
|
|
not outBarrier(node1, config) and
|
|
not inBarrier(node2, config) and
|
|
not fullBarrier(node1, config) and
|
|
not fullBarrier(node2, config)
|
|
}
|
|
|
|
/**
|
|
* Holds if the additional step from `node1` to `node2` jumps between callables.
|
|
*/
|
|
private predicate additionalJumpStep(Node node1, Node node2, Configuration config) {
|
|
exists(DataFlowCallable callable1 |
|
|
isAdditionalFlowStep(node1, node2, callable1, config) and
|
|
node2.getEnclosingCallable() != callable1 and
|
|
not outBarrier(node1, config) and
|
|
not inBarrier(node2, config) and
|
|
not fullBarrier(node1, config) and
|
|
not fullBarrier(node2, config)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if field flow should be used for the given configuration.
|
|
*/
|
|
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
|
|
|
|
/**
|
|
* Holds if `node` is reachable from a source in the configuration `config`.
|
|
*
|
|
* The Boolean `fromArg` records whether the node is reached through an
|
|
* argument in a call.
|
|
*/
|
|
private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) {
|
|
not fullBarrier(node, config) and
|
|
(
|
|
config.isSource(node) and
|
|
fromArg = false
|
|
or
|
|
exists(Node mid |
|
|
nodeCandFwd1(mid, fromArg, config) and
|
|
localFlowStep(mid, node, config)
|
|
)
|
|
or
|
|
exists(Node mid |
|
|
nodeCandFwd1(mid, fromArg, config) and
|
|
additionalLocalFlowStep(mid, node, config)
|
|
)
|
|
or
|
|
exists(Node mid |
|
|
nodeCandFwd1(mid, config) and
|
|
jumpStep(mid, node, config) and
|
|
fromArg = false
|
|
)
|
|
or
|
|
exists(Node mid |
|
|
nodeCandFwd1(mid, config) and
|
|
additionalJumpStep(mid, node, config) and
|
|
fromArg = false
|
|
)
|
|
or
|
|
// store
|
|
exists(Node mid |
|
|
useFieldFlow(config) and
|
|
nodeCandFwd1(mid, fromArg, config) and
|
|
store(mid, _, node, _) and
|
|
not outBarrier(mid, config)
|
|
)
|
|
or
|
|
// read
|
|
exists(Content c |
|
|
nodeCandFwd1Read(c, node, fromArg, config) and
|
|
nodeCandFwd1IsStored(c, config) and
|
|
not inBarrier(node, config)
|
|
)
|
|
or
|
|
// flow into a callable
|
|
exists(Node arg |
|
|
nodeCandFwd1(arg, config) and
|
|
viableParamArg(_, node, arg) and
|
|
fromArg = true
|
|
)
|
|
or
|
|
// flow out of a callable
|
|
exists(DataFlowCall call |
|
|
nodeCandFwd1Out(call, node, false, config) and
|
|
fromArg = false
|
|
or
|
|
nodeCandFwd1OutFromArg(call, node, config) and
|
|
nodeCandFwd1IsEntered(call, fromArg, config)
|
|
)
|
|
)
|
|
}
|
|
|
|
private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, config) }
|
|
|
|
pragma[nomagic]
|
|
private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) {
|
|
exists(Node mid |
|
|
nodeCandFwd1(mid, fromArg, config) and
|
|
read(mid, c, node)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate nodeCandFwd1IsStored(Content c, Configuration config) {
|
|
exists(Node mid, Node node, TypedContent tc |
|
|
not fullBarrier(node, config) and
|
|
useFieldFlow(config) and
|
|
nodeCandFwd1(mid, config) and
|
|
store(mid, tc, node, _) and
|
|
c = tc.getContent()
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate nodeCandFwd1ReturnPosition(
|
|
ReturnPosition pos, boolean fromArg, Configuration config
|
|
) {
|
|
exists(ReturnNodeExt ret |
|
|
nodeCandFwd1(ret, fromArg, config) and
|
|
getReturnPosition(ret) = pos
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate nodeCandFwd1Out(DataFlowCall call, Node out, boolean fromArg, Configuration config) {
|
|
exists(ReturnPosition pos |
|
|
nodeCandFwd1ReturnPosition(pos, fromArg, config) and
|
|
viableReturnPosOut(call, pos, out)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) {
|
|
nodeCandFwd1Out(call, node, true, config)
|
|
}
|
|
|
|
/**
|
|
* Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) {
|
|
exists(ArgumentNode arg |
|
|
nodeCandFwd1(arg, fromArg, config) and
|
|
viableParamArg(call, _, arg)
|
|
)
|
|
}
|
|
|
|
bindingset[result, b]
|
|
private boolean unbindBool(boolean b) { result != b.booleanNot() }
|
|
|
|
/**
|
|
* Holds if `node` is part of a path from a source to a sink in the
|
|
* configuration `config`.
|
|
*
|
|
* The Boolean `toReturn` records whether the node must be returned from
|
|
* the enclosing callable in order to reach a sink.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate nodeCand1(Node node, boolean toReturn, Configuration config) {
|
|
nodeCand1_0(node, toReturn, config) and
|
|
nodeCandFwd1(node, config)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) {
|
|
nodeCandFwd1(node, config) and
|
|
config.isSink(node) and
|
|
toReturn = false
|
|
or
|
|
exists(Node mid |
|
|
localFlowStep(node, mid, config) and
|
|
nodeCand1(mid, toReturn, config)
|
|
)
|
|
or
|
|
exists(Node mid |
|
|
additionalLocalFlowStep(node, mid, config) and
|
|
nodeCand1(mid, toReturn, config)
|
|
)
|
|
or
|
|
exists(Node mid |
|
|
jumpStep(node, mid, config) and
|
|
nodeCand1(mid, _, config) and
|
|
toReturn = false
|
|
)
|
|
or
|
|
exists(Node mid |
|
|
additionalJumpStep(node, mid, config) and
|
|
nodeCand1(mid, _, config) and
|
|
toReturn = false
|
|
)
|
|
or
|
|
// store
|
|
exists(Content c |
|
|
nodeCand1Store(c, node, toReturn, config) and
|
|
nodeCand1IsRead(c, config)
|
|
)
|
|
or
|
|
// read
|
|
exists(Node mid, Content c |
|
|
read(node, c, mid) and
|
|
nodeCandFwd1IsStored(c, unbind(config)) and
|
|
nodeCand1(mid, toReturn, config)
|
|
)
|
|
or
|
|
// flow into a callable
|
|
exists(DataFlowCall call |
|
|
nodeCand1In(call, node, false, config) and
|
|
toReturn = false
|
|
or
|
|
nodeCand1InToReturn(call, node, config) and
|
|
nodeCand1IsReturned(call, toReturn, config)
|
|
)
|
|
or
|
|
// flow out of a callable
|
|
exists(ReturnPosition pos |
|
|
nodeCand1Out(pos, config) and
|
|
getReturnPosition(node) = pos and
|
|
toReturn = true
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `c` is the target of a read in the flow covered by `nodeCand1`.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate nodeCand1IsRead(Content c, Configuration config) {
|
|
exists(Node mid, Node node |
|
|
useFieldFlow(config) and
|
|
nodeCandFwd1(node, unbind(config)) and
|
|
read(node, c, mid) and
|
|
nodeCandFwd1IsStored(c, unbind(config)) and
|
|
nodeCand1(mid, _, config)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) {
|
|
exists(Node mid, TypedContent tc |
|
|
nodeCand1(mid, toReturn, config) and
|
|
nodeCandFwd1IsStored(c, unbind(config)) and
|
|
store(node, tc, mid, _) and
|
|
c = tc.getContent()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `c` is the target of both a read and a store in the flow covered
|
|
* by `nodeCand1`.
|
|
*/
|
|
private predicate nodeCand1IsReadAndStored(Content c, Configuration conf) {
|
|
nodeCand1IsRead(c, conf) and
|
|
nodeCand1Store(c, _, _, conf)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate viableReturnPosOutNodeCandFwd1(
|
|
DataFlowCall call, ReturnPosition pos, Node out, Configuration config
|
|
) {
|
|
nodeCandFwd1ReturnPosition(pos, _, config) and
|
|
viableReturnPosOut(call, pos, out)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate nodeCand1Out(ReturnPosition pos, Configuration config) {
|
|
exists(DataFlowCall call, Node out |
|
|
nodeCand1(out, _, config) and
|
|
viableReturnPosOutNodeCandFwd1(call, pos, out, config)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate viableParamArgNodeCandFwd1(
|
|
DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config
|
|
) {
|
|
viableParamArg(call, p, arg) and
|
|
nodeCandFwd1(arg, config)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate nodeCand1In(
|
|
DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config
|
|
) {
|
|
exists(ParameterNode p |
|
|
nodeCand1(p, toReturn, config) and
|
|
viableParamArgNodeCandFwd1(call, p, arg, config)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) {
|
|
nodeCand1In(call, arg, true, config)
|
|
}
|
|
|
|
/**
|
|
* Holds if an output from `call` is reached in the flow covered by `nodeCand1`.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) {
|
|
exists(Node out |
|
|
nodeCand1(out, toReturn, config) and
|
|
nodeCandFwd1OutFromArg(call, out, config)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) }
|
|
|
|
private predicate throughFlowNodeCand1(Node node, Configuration config) {
|
|
nodeCand1(node, true, config) and
|
|
not fullBarrier(node, config) and
|
|
not inBarrier(node, config) and
|
|
not outBarrier(node, config)
|
|
}
|
|
|
|
/** Holds if flow may return from `callable`. */
|
|
pragma[nomagic]
|
|
private predicate returnFlowCallableNodeCand1(
|
|
DataFlowCallable callable, ReturnKindExt kind, Configuration config
|
|
) {
|
|
exists(ReturnNodeExt ret |
|
|
throughFlowNodeCand1(ret, config) and
|
|
callable = ret.getEnclosingCallable() and
|
|
kind = ret.getKind()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if flow may enter through `p` and reach a return node making `p` a
|
|
* candidate for the origin of a summary.
|
|
*/
|
|
private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration config) {
|
|
exists(ReturnKindExt kind |
|
|
throughFlowNodeCand1(p, config) and
|
|
returnFlowCallableNodeCand1(p.getEnclosingCallable(), kind, config) and
|
|
// we don't expect a parameter to return stored in itself
|
|
not exists(int pos |
|
|
kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos)
|
|
)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) {
|
|
exists(TypedContent tc |
|
|
nodeCand1IsReadAndStored(c, config) and
|
|
nodeCand1(n2, unbind(config)) and
|
|
store(n1, tc, n2, _) and
|
|
c = tc.getContent()
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate read(Node n1, Content c, Node n2, Configuration config) {
|
|
nodeCand1IsReadAndStored(c, config) and
|
|
nodeCand1(n2, unbind(config)) and
|
|
read(n1, c, n2)
|
|
}
|
|
|
|
pragma[noinline]
|
|
private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) {
|
|
nodeCand1(node1, config) and
|
|
localFlowStep(node1, node2, config)
|
|
}
|
|
|
|
pragma[noinline]
|
|
private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) {
|
|
nodeCand1(node1, config) and
|
|
additionalLocalFlowStep(node1, node2, config)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate viableReturnPosOutNodeCand1(
|
|
DataFlowCall call, ReturnPosition pos, Node out, Configuration config
|
|
) {
|
|
nodeCand1(out, _, config) and
|
|
viableReturnPosOutNodeCandFwd1(call, pos, out, config)
|
|
}
|
|
|
|
/**
|
|
* Holds if data can flow out of `call` from `ret` to `out`, either
|
|
* through a `ReturnNode` or through an argument that has been mutated, and
|
|
* that this step is part of a path from a source to a sink.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate flowOutOfCallNodeCand1(
|
|
DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config
|
|
) {
|
|
viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and
|
|
nodeCand1(ret, config) and
|
|
not outBarrier(ret, config) and
|
|
not inBarrier(out, config)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate viableParamArgNodeCand1(
|
|
DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config
|
|
) {
|
|
viableParamArgNodeCandFwd1(call, p, arg, config) and
|
|
nodeCand1(arg, config)
|
|
}
|
|
|
|
/**
|
|
* Holds if data can flow into `call` and that this step is part of a
|
|
* path from a source to a sink.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate flowIntoCallNodeCand1(
|
|
DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config
|
|
) {
|
|
viableParamArgNodeCand1(call, p, arg, config) and
|
|
nodeCand1(p, config) and
|
|
not outBarrier(arg, config) and
|
|
not inBarrier(p, config)
|
|
}
|
|
|
|
/**
|
|
* Gets the amount of forward branching on the origin of a cross-call path
|
|
* edge in the graph of paths between sources and sinks that ignores call
|
|
* contexts.
|
|
*/
|
|
private int branch(Node n1, Configuration conf) {
|
|
result =
|
|
strictcount(Node n |
|
|
flowOutOfCallNodeCand1(_, n1, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets the amount of backward branching on the target of a cross-call path
|
|
* edge in the graph of paths between sources and sinks that ignores call
|
|
* contexts.
|
|
*/
|
|
private int join(Node n2, Configuration conf) {
|
|
result =
|
|
strictcount(Node n |
|
|
flowOutOfCallNodeCand1(_, n, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if data can flow out of `call` from `ret` to `out`, either
|
|
* through a `ReturnNode` or through an argument that has been mutated, and
|
|
* that this step is part of a path from a source to a sink. The
|
|
* `allowsFieldFlow` flag indicates whether the branching is within the limit
|
|
* specified by the configuration.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate flowOutOfCallNodeCand1(
|
|
DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config
|
|
) {
|
|
flowOutOfCallNodeCand1(call, ret, out, config) and
|
|
exists(int b, int j |
|
|
b = branch(ret, config) and
|
|
j = join(out, config) and
|
|
if b.minimum(j) <= config.fieldFlowBranchLimit()
|
|
then allowsFieldFlow = true
|
|
else allowsFieldFlow = false
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if data can flow into `call` and that this step is part of a
|
|
* path from a source to a sink. The `allowsFieldFlow` flag indicates whether
|
|
* the branching is within the limit specified by the configuration.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate flowIntoCallNodeCand1(
|
|
DataFlowCall call, ArgumentNode arg, ParameterNode p, boolean allowsFieldFlow,
|
|
Configuration config
|
|
) {
|
|
flowIntoCallNodeCand1(call, arg, p, config) and
|
|
exists(int b, int j |
|
|
b = branch(arg, config) and
|
|
j = join(p, config) and
|
|
if b.minimum(j) <= config.fieldFlowBranchLimit()
|
|
then allowsFieldFlow = true
|
|
else allowsFieldFlow = false
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `node` is reachable from a source in the configuration `config`.
|
|
* The Boolean `stored` records whether the tracked value is stored into a
|
|
* field of `node`.
|
|
*
|
|
* The Boolean `fromArg` records whether the node is reached through an
|
|
* argument in a call, and if so, `argStored` records whether the tracked
|
|
* value was stored into a field of the argument.
|
|
*/
|
|
private predicate nodeCandFwd2(
|
|
Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config
|
|
) {
|
|
nodeCand1(node, config) and
|
|
config.isSource(node) and
|
|
fromArg = false and
|
|
argStored = TBooleanNone() and
|
|
stored = false
|
|
or
|
|
nodeCand1(node, unbind(config)) and
|
|
(
|
|
exists(Node mid |
|
|
nodeCandFwd2(mid, fromArg, argStored, stored, config) and
|
|
localFlowStepNodeCand1(mid, node, config)
|
|
)
|
|
or
|
|
exists(Node mid |
|
|
nodeCandFwd2(mid, fromArg, argStored, stored, config) and
|
|
additionalLocalFlowStepNodeCand1(mid, node, config) and
|
|
stored = false
|
|
)
|
|
or
|
|
exists(Node mid |
|
|
nodeCandFwd2(mid, _, _, stored, config) and
|
|
jumpStep(mid, node, config) and
|
|
fromArg = false and
|
|
argStored = TBooleanNone()
|
|
)
|
|
or
|
|
exists(Node mid |
|
|
nodeCandFwd2(mid, _, _, stored, config) and
|
|
additionalJumpStep(mid, node, config) and
|
|
fromArg = false and
|
|
argStored = TBooleanNone() and
|
|
stored = false
|
|
)
|
|
or
|
|
// store
|
|
exists(Node mid |
|
|
nodeCandFwd2(mid, fromArg, argStored, _, config) and
|
|
storeCand1(mid, _, node, config) and
|
|
stored = true
|
|
)
|
|
or
|
|
// read
|
|
exists(Content c |
|
|
nodeCandFwd2Read(c, node, fromArg, argStored, config) and
|
|
nodeCandFwd2IsStored(c, stored, config)
|
|
)
|
|
or
|
|
// flow into a callable
|
|
nodeCandFwd2In(_, node, _, _, stored, config) and
|
|
fromArg = true and
|
|
if parameterThroughFlowNodeCand1(node, config)
|
|
then argStored = TBooleanSome(stored)
|
|
else argStored = TBooleanNone()
|
|
or
|
|
// flow out of a callable
|
|
exists(DataFlowCall call |
|
|
nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and
|
|
fromArg = false
|
|
or
|
|
exists(boolean argStored0 |
|
|
nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and
|
|
nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config)
|
|
)
|
|
)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`.
|
|
*/
|
|
pragma[noinline]
|
|
private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) {
|
|
exists(Node mid, Node node |
|
|
useFieldFlow(config) and
|
|
nodeCand1(node, unbind(config)) and
|
|
nodeCandFwd2(mid, _, _, stored, config) and
|
|
storeCand1(mid, c, node, config)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate nodeCandFwd2Read(
|
|
Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config
|
|
) {
|
|
exists(Node mid |
|
|
nodeCandFwd2(mid, fromArg, argStored, true, config) and
|
|
read(mid, c, node, config)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate nodeCandFwd2In(
|
|
DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored,
|
|
Configuration config
|
|
) {
|
|
exists(ArgumentNode arg, boolean allowsFieldFlow |
|
|
nodeCandFwd2(arg, fromArg, argStored, stored, config) and
|
|
flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config)
|
|
|
|
|
stored = false or allowsFieldFlow = true
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate nodeCandFwd2Out(
|
|
DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored,
|
|
Configuration config
|
|
) {
|
|
exists(ReturnNodeExt ret, boolean allowsFieldFlow |
|
|
nodeCandFwd2(ret, fromArg, argStored, stored, config) and
|
|
flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config)
|
|
|
|
|
stored = false or allowsFieldFlow = true
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate nodeCandFwd2OutFromArg(
|
|
DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config
|
|
) {
|
|
nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config)
|
|
}
|
|
|
|
/**
|
|
* Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate nodeCandFwd2IsEntered(
|
|
DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config
|
|
) {
|
|
exists(ParameterNode p |
|
|
nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and
|
|
parameterThroughFlowNodeCand1(p, config)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `node` is part of a path from a source to a sink in the
|
|
* configuration `config`. The Boolean `read` records whether the tracked
|
|
* value must be read from a field of `node` in order to reach a sink.
|
|
*
|
|
* The Boolean `toReturn` records whether the node must be returned from
|
|
* the enclosing callable in order to reach a sink, and if so, `returnRead`
|
|
* records whether a field must be read from the returned value.
|
|
*/
|
|
private predicate nodeCand2(
|
|
Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config
|
|
) {
|
|
nodeCandFwd2(node, _, _, false, config) and
|
|
config.isSink(node) and
|
|
toReturn = false and
|
|
returnRead = TBooleanNone() and
|
|
read = false
|
|
or
|
|
nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and
|
|
(
|
|
exists(Node mid |
|
|
localFlowStepNodeCand1(node, mid, config) and
|
|
nodeCand2(mid, toReturn, returnRead, read, config)
|
|
)
|
|
or
|
|
exists(Node mid |
|
|
additionalLocalFlowStepNodeCand1(node, mid, config) and
|
|
nodeCand2(mid, toReturn, returnRead, read, config) and
|
|
read = false
|
|
)
|
|
or
|
|
exists(Node mid |
|
|
jumpStep(node, mid, config) and
|
|
nodeCand2(mid, _, _, read, config) and
|
|
toReturn = false and
|
|
returnRead = TBooleanNone()
|
|
)
|
|
or
|
|
exists(Node mid |
|
|
additionalJumpStep(node, mid, config) and
|
|
nodeCand2(mid, _, _, read, config) and
|
|
toReturn = false and
|
|
returnRead = TBooleanNone() and
|
|
read = false
|
|
)
|
|
or
|
|
// store
|
|
exists(Content c |
|
|
nodeCand2Store(c, node, toReturn, returnRead, read, config) and
|
|
nodeCand2IsRead(c, read, config)
|
|
)
|
|
or
|
|
// read
|
|
exists(Node mid, Content c, boolean read0 |
|
|
read(node, c, mid, config) and
|
|
nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and
|
|
nodeCand2(mid, toReturn, returnRead, read0, config) and
|
|
read = true
|
|
)
|
|
or
|
|
// flow into a callable
|
|
exists(DataFlowCall call |
|
|
nodeCand2In(call, node, toReturn, returnRead, read, config) and
|
|
toReturn = false
|
|
or
|
|
exists(boolean returnRead0 |
|
|
nodeCand2InToReturn(call, node, returnRead0, read, config) and
|
|
nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config)
|
|
)
|
|
)
|
|
or
|
|
// flow out of a callable
|
|
nodeCand2Out(_, node, _, _, read, config) and
|
|
toReturn = true and
|
|
if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config)
|
|
then returnRead = TBooleanSome(read)
|
|
else returnRead = TBooleanNone()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `c` is the target of a read in the flow covered by `nodeCand2`.
|
|
*/
|
|
pragma[noinline]
|
|
private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) {
|
|
exists(Node mid, Node node |
|
|
useFieldFlow(config) and
|
|
nodeCandFwd2(node, _, _, true, unbind(config)) and
|
|
read(node, c, mid, config) and
|
|
nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and
|
|
nodeCand2(mid, _, _, read, config)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate nodeCand2Store(
|
|
Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored,
|
|
Configuration config
|
|
) {
|
|
exists(Node mid |
|
|
storeCand1(node, c, mid, config) and
|
|
nodeCand2(mid, toReturn, returnRead, true, config) and
|
|
nodeCandFwd2(node, _, _, stored, unbind(config))
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `c` is the target of a store in the flow covered by `nodeCand2`.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) {
|
|
exists(Node node |
|
|
nodeCand2Store(c, node, _, _, stored, conf) and
|
|
nodeCand2(node, _, _, stored, conf)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `c` is the target of both a store and a read in the path graph
|
|
* covered by `nodeCand2`.
|
|
*/
|
|
pragma[noinline]
|
|
private predicate nodeCand2IsReadAndStored(Content c, Configuration conf) {
|
|
exists(boolean apNonEmpty |
|
|
nodeCand2IsStored(c, apNonEmpty, conf) and
|
|
nodeCand2IsRead(c, apNonEmpty, conf)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate nodeCand2Out(
|
|
DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read,
|
|
Configuration config
|
|
) {
|
|
exists(Node out, boolean allowsFieldFlow |
|
|
nodeCand2(out, toReturn, returnRead, read, config) and
|
|
flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config)
|
|
|
|
|
read = false or allowsFieldFlow = true
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate nodeCand2In(
|
|
DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read,
|
|
Configuration config
|
|
) {
|
|
exists(ParameterNode p, boolean allowsFieldFlow |
|
|
nodeCand2(p, toReturn, returnRead, read, config) and
|
|
flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config)
|
|
|
|
|
read = false or allowsFieldFlow = true
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate nodeCand2InToReturn(
|
|
DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config
|
|
) {
|
|
nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config)
|
|
}
|
|
|
|
/**
|
|
* Holds if an output from `call` is reached in the flow covered by `nodeCand2`.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate nodeCand2IsReturned(
|
|
DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config
|
|
) {
|
|
exists(ReturnNodeExt ret |
|
|
nodeCand2Out(call, ret, toReturn, returnRead, read, config) and
|
|
nodeCandFwd2(ret, true, TBooleanSome(_), read, config)
|
|
)
|
|
}
|
|
|
|
private predicate nodeCand2(Node node, Configuration config) { nodeCand2(node, _, _, _, config) }
|
|
|
|
pragma[nomagic]
|
|
private predicate flowOutOfCallNodeCand2(
|
|
DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config
|
|
) {
|
|
flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and
|
|
nodeCand2(node2, config) and
|
|
nodeCand2(node1, unbind(config))
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowIntoCallNodeCand2(
|
|
DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow,
|
|
Configuration config
|
|
) {
|
|
flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and
|
|
nodeCand2(node2, config) and
|
|
nodeCand2(node1, unbind(config))
|
|
}
|
|
|
|
private module LocalFlowBigStep {
|
|
/**
|
|
* A node where some checking is required, and hence the big-step relation
|
|
* is not allowed to step over.
|
|
*/
|
|
private class FlowCheckNode extends Node {
|
|
FlowCheckNode() {
|
|
this instanceof CastNode or
|
|
clearsContent(this, _)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Holds if `node` can be the first node in a maximal subsequence of local
|
|
* flow steps in a dataflow path.
|
|
*/
|
|
predicate localFlowEntry(Node node, Configuration config) {
|
|
nodeCand2(node, config) and
|
|
(
|
|
config.isSource(node) or
|
|
jumpStep(_, node, config) or
|
|
additionalJumpStep(_, node, config) or
|
|
node instanceof ParameterNode or
|
|
node instanceof OutNodeExt or
|
|
store(_, _, node, _) or
|
|
read(_, _, node) or
|
|
node instanceof FlowCheckNode
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `node` can be the last node in a maximal subsequence of local
|
|
* flow steps in a dataflow path.
|
|
*/
|
|
private predicate localFlowExit(Node node, Configuration config) {
|
|
exists(Node next | nodeCand2(next, config) |
|
|
jumpStep(node, next, config) or
|
|
additionalJumpStep(node, next, config) or
|
|
flowIntoCallNodeCand1(_, node, next, config) or
|
|
flowOutOfCallNodeCand1(_, node, next, config) or
|
|
store(node, _, next, _) or
|
|
read(node, _, next)
|
|
)
|
|
or
|
|
node instanceof FlowCheckNode
|
|
or
|
|
config.isSink(node)
|
|
}
|
|
|
|
pragma[noinline]
|
|
private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) {
|
|
additionalLocalFlowStepNodeCand1(node1, node2, config) and
|
|
nodeCand2(node1, _, _, false, config) and
|
|
nodeCand2(node2, _, _, false, unbind(config))
|
|
}
|
|
|
|
/**
|
|
* Holds if the local path from `node1` to `node2` is a prefix of a maximal
|
|
* subsequence of local flow steps in a dataflow path.
|
|
*
|
|
* This is the transitive closure of `[additional]localFlowStep` beginning
|
|
* at `localFlowEntry`.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate localFlowStepPlus(
|
|
Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config,
|
|
LocalCallContext cc
|
|
) {
|
|
not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and
|
|
(
|
|
localFlowEntry(node1, config) and
|
|
(
|
|
localFlowStepNodeCand1(node1, node2, config) and
|
|
preservesValue = true and
|
|
t = getNodeType(node1)
|
|
or
|
|
additionalLocalFlowStepNodeCand2(node1, node2, config) and
|
|
preservesValue = false and
|
|
t = getNodeType(node2)
|
|
) and
|
|
node1 != node2 and
|
|
cc.relevantFor(node1.getEnclosingCallable()) and
|
|
not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and
|
|
nodeCand2(node2, unbind(config))
|
|
or
|
|
exists(Node mid |
|
|
localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and
|
|
localFlowStepNodeCand1(mid, node2, config) and
|
|
not mid instanceof FlowCheckNode and
|
|
nodeCand2(node2, unbind(config))
|
|
)
|
|
or
|
|
exists(Node mid |
|
|
localFlowStepPlus(node1, mid, _, _, config, cc) and
|
|
additionalLocalFlowStepNodeCand2(mid, node2, config) and
|
|
not mid instanceof FlowCheckNode and
|
|
preservesValue = false and
|
|
t = getNodeType(node2) and
|
|
nodeCand2(node2, unbind(config))
|
|
)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `node1` can step to `node2` in one or more local steps and this
|
|
* path can occur as a maximal subsequence of local steps in a dataflow path.
|
|
*/
|
|
pragma[nomagic]
|
|
predicate localFlowBigStep(
|
|
Node node1, Node node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config,
|
|
LocalCallContext callContext
|
|
) {
|
|
localFlowStepPlus(node1, node2, preservesValue, apf.getType(), config, callContext) and
|
|
localFlowExit(node2, config)
|
|
}
|
|
}
|
|
|
|
private import LocalFlowBigStep
|
|
|
|
pragma[nomagic]
|
|
private predicate readCand2(Node node1, Content c, Node node2, Configuration config) {
|
|
read(node1, c, node2, config) and
|
|
nodeCand2(node1, _, _, true, unbind(config)) and
|
|
nodeCand2(node2, config) and
|
|
nodeCand2IsReadAndStored(c, unbind(config))
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate storeCand2(
|
|
Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config
|
|
) {
|
|
store(node1, tc, node2, contentType) and
|
|
nodeCand2(node1, config) and
|
|
nodeCand2(node2, _, _, true, unbind(config)) and
|
|
nodeCand2IsReadAndStored(tc.getContent(), unbind(config))
|
|
}
|
|
|
|
/**
|
|
* Holds if `node` is reachable with access path front `apf` from a
|
|
* source in the configuration `config`.
|
|
*
|
|
* The Boolean `fromArg` records whether the node is reached through an
|
|
* argument in a call, and if so, `argApf` records the front of the
|
|
* access path of that argument.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate flowCandFwd(
|
|
Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf,
|
|
Configuration config
|
|
) {
|
|
flowCandFwd0(node, fromArg, argApf, apf, config) and
|
|
not apf.isClearedAt(node) and
|
|
if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any()
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowCandFwd0(
|
|
Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf,
|
|
Configuration config
|
|
) {
|
|
nodeCand2(node, _, _, false, config) and
|
|
config.isSource(node) and
|
|
fromArg = false and
|
|
argApf = TAccessPathFrontNone() and
|
|
apf = TFrontNil(getNodeType(node))
|
|
or
|
|
exists(Node mid |
|
|
flowCandFwd(mid, fromArg, argApf, apf, config) and
|
|
localFlowBigStep(mid, node, true, _, config, _)
|
|
)
|
|
or
|
|
exists(Node mid, AccessPathFrontNil nil |
|
|
flowCandFwd(mid, fromArg, argApf, nil, config) and
|
|
localFlowBigStep(mid, node, false, apf, config, _)
|
|
)
|
|
or
|
|
exists(Node mid |
|
|
flowCandFwd(mid, _, _, apf, config) and
|
|
nodeCand2(node, unbind(config)) and
|
|
jumpStep(mid, node, config) and
|
|
fromArg = false and
|
|
argApf = TAccessPathFrontNone()
|
|
)
|
|
or
|
|
exists(Node mid, AccessPathFrontNil nil |
|
|
flowCandFwd(mid, _, _, nil, config) and
|
|
nodeCand2(node, unbind(config)) and
|
|
additionalJumpStep(mid, node, config) and
|
|
fromArg = false and
|
|
argApf = TAccessPathFrontNone() and
|
|
apf = TFrontNil(getNodeType(node))
|
|
)
|
|
or
|
|
// store
|
|
exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType |
|
|
flowCandFwd(mid, fromArg, argApf, apf0, config) and
|
|
storeCand2(mid, tc, node, contentType, config) and
|
|
nodeCand2(node, _, _, true, unbind(config)) and
|
|
apf.headUsesContent(tc) and
|
|
compatibleTypes(apf0.getType(), contentType)
|
|
)
|
|
or
|
|
// read
|
|
exists(TypedContent tc |
|
|
flowCandFwdRead(tc, node, fromArg, argApf, config) and
|
|
flowCandFwdConsCand(tc, apf, config) and
|
|
nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config))
|
|
)
|
|
or
|
|
// flow into a callable
|
|
flowCandFwdIn(_, node, _, _, apf, config) and
|
|
fromArg = true and
|
|
if nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config)
|
|
then argApf = TAccessPathFrontSome(apf)
|
|
else argApf = TAccessPathFrontNone()
|
|
or
|
|
// flow out of a callable
|
|
exists(DataFlowCall call |
|
|
flowCandFwdOut(call, node, fromArg, argApf, apf, config) and
|
|
fromArg = false
|
|
or
|
|
exists(AccessPathFront argApf0 |
|
|
flowCandFwdOutFromArg(call, node, argApf0, apf, config) and
|
|
flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config)
|
|
)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) {
|
|
exists(Node mid, Node n, DataFlowType contentType |
|
|
flowCandFwd(mid, _, _, apf, config) and
|
|
storeCand2(mid, tc, n, contentType, config) and
|
|
nodeCand2(n, _, _, true, unbind(config)) and
|
|
compatibleTypes(apf.getType(), contentType)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowCandFwdRead0(
|
|
Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, AccessPathFrontOption argApf,
|
|
AccessPathFrontHead apf, Configuration config
|
|
) {
|
|
flowCandFwd(node1, fromArg, argApf, apf, config) and
|
|
readCand2(node1, c, node2, config) and
|
|
apf.headUsesContent(tc)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowCandFwdRead(
|
|
TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config
|
|
) {
|
|
flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowCandFwdIn(
|
|
DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf,
|
|
AccessPathFront apf, Configuration config
|
|
) {
|
|
exists(ArgumentNode arg, boolean allowsFieldFlow |
|
|
flowCandFwd(arg, fromArg, argApf, apf, config) and
|
|
flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config)
|
|
|
|
|
apf instanceof AccessPathFrontNil or allowsFieldFlow = true
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowCandFwdOut(
|
|
DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf,
|
|
Configuration config
|
|
) {
|
|
exists(ReturnNodeExt ret, boolean allowsFieldFlow |
|
|
flowCandFwd(ret, fromArg, argApf, apf, config) and
|
|
flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config)
|
|
|
|
|
apf instanceof AccessPathFrontNil or allowsFieldFlow = true
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowCandFwdOutFromArg(
|
|
DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config
|
|
) {
|
|
flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config)
|
|
}
|
|
|
|
/**
|
|
* Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate flowCandFwdIsEntered(
|
|
DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf,
|
|
Configuration config
|
|
) {
|
|
exists(ParameterNode p |
|
|
flowCandFwdIn(call, p, fromArg, argApf, apf, config) and
|
|
nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `node` with access path front `apf` is part of a path from a
|
|
* source to a sink in the configuration `config`.
|
|
*
|
|
* The Boolean `toReturn` records whether the node must be returned from
|
|
* the enclosing callable in order to reach a sink, and if so, `returnApf`
|
|
* records the front of the access path of the returned value.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate flowCand(
|
|
Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf,
|
|
Configuration config
|
|
) {
|
|
flowCand0(node, toReturn, returnApf, apf, config) and
|
|
flowCandFwd(node, _, _, apf, config)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowCand0(
|
|
Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf,
|
|
Configuration config
|
|
) {
|
|
flowCandFwd(node, _, _, apf, config) and
|
|
config.isSink(node) and
|
|
toReturn = false and
|
|
returnApf = TAccessPathFrontNone() and
|
|
apf instanceof AccessPathFrontNil
|
|
or
|
|
exists(Node mid |
|
|
localFlowBigStep(node, mid, true, _, config, _) and
|
|
flowCand(mid, toReturn, returnApf, apf, config)
|
|
)
|
|
or
|
|
exists(Node mid, AccessPathFrontNil nil |
|
|
flowCandFwd(node, _, _, apf, config) and
|
|
localFlowBigStep(node, mid, false, _, config, _) and
|
|
flowCand(mid, toReturn, returnApf, nil, config) and
|
|
apf instanceof AccessPathFrontNil
|
|
)
|
|
or
|
|
exists(Node mid |
|
|
jumpStep(node, mid, config) and
|
|
flowCand(mid, _, _, apf, config) and
|
|
toReturn = false and
|
|
returnApf = TAccessPathFrontNone()
|
|
)
|
|
or
|
|
exists(Node mid, AccessPathFrontNil nil |
|
|
flowCandFwd(node, _, _, apf, config) and
|
|
additionalJumpStep(node, mid, config) and
|
|
flowCand(mid, _, _, nil, config) and
|
|
toReturn = false and
|
|
returnApf = TAccessPathFrontNone() and
|
|
apf instanceof AccessPathFrontNil
|
|
)
|
|
or
|
|
// store
|
|
exists(TypedContent tc |
|
|
flowCandStore(node, tc, apf, toReturn, returnApf, config) and
|
|
flowCandConsCand(tc, apf, config)
|
|
)
|
|
or
|
|
// read
|
|
exists(TypedContent tc, AccessPathFront apf0 |
|
|
flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and
|
|
flowCandFwdConsCand(tc, apf0, config)
|
|
)
|
|
or
|
|
// flow into a callable
|
|
exists(DataFlowCall call |
|
|
flowCandIn(call, node, toReturn, returnApf, apf, config) and
|
|
toReturn = false
|
|
or
|
|
exists(AccessPathFront returnApf0 |
|
|
flowCandInToReturn(call, node, returnApf0, apf, config) and
|
|
flowCandIsReturned(call, toReturn, returnApf, returnApf0, config)
|
|
)
|
|
)
|
|
or
|
|
// flow out of a callable
|
|
flowCandOut(_, node, _, _, apf, config) and
|
|
toReturn = true and
|
|
if flowCandFwd(node, true, _, apf, config)
|
|
then returnApf = TAccessPathFrontSome(apf)
|
|
else returnApf = TAccessPathFrontNone()
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate readCandFwd(
|
|
Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config
|
|
) {
|
|
flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowCandRead(
|
|
Node node, TypedContent tc, AccessPathFront apf, boolean toReturn,
|
|
AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config
|
|
) {
|
|
exists(Node mid |
|
|
readCandFwd(node, tc, apf, mid, config) and
|
|
flowCand(mid, toReturn, returnApf, apf0, config)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowCandStore(
|
|
Node node, TypedContent tc, AccessPathFront apf, boolean toReturn,
|
|
AccessPathFrontOption returnApf, Configuration config
|
|
) {
|
|
exists(Node mid |
|
|
flowCandFwd(node, _, _, apf, config) and
|
|
storeCand2(node, tc, mid, _, unbind(config)) and
|
|
flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config))
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) {
|
|
flowCandFwdConsCand(tc, apf, config) and
|
|
flowCandRead(_, tc, _, _, _, apf, config)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowCandOut(
|
|
DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf,
|
|
AccessPathFront apf, Configuration config
|
|
) {
|
|
exists(Node out, boolean allowsFieldFlow |
|
|
flowCand(out, toReturn, returnApf, apf, config) and
|
|
flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config)
|
|
|
|
|
apf instanceof AccessPathFrontNil or allowsFieldFlow = true
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowCandIn(
|
|
DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf,
|
|
AccessPathFront apf, Configuration config
|
|
) {
|
|
exists(ParameterNode p, boolean allowsFieldFlow |
|
|
flowCand(p, toReturn, returnApf, apf, config) and
|
|
flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config)
|
|
|
|
|
apf instanceof AccessPathFrontNil or allowsFieldFlow = true
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowCandInToReturn(
|
|
DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf,
|
|
Configuration config
|
|
) {
|
|
flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, config)
|
|
}
|
|
|
|
/**
|
|
* Holds if an output from `call` is reached in the flow covered by `flowCand`.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate flowCandIsReturned(
|
|
DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf,
|
|
Configuration config
|
|
) {
|
|
exists(ReturnNodeExt ret |
|
|
flowCandOut(call, ret, toReturn, returnApf, apf, config) and
|
|
flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config)
|
|
)
|
|
}
|
|
|
|
private newtype TAccessPath =
|
|
TNil(DataFlowType t) or
|
|
TConsNil(TypedContent tc, DataFlowType t) { flowCandConsCand(tc, TFrontNil(t), _) } or
|
|
TConsCons(TypedContent tc1, TypedContent tc2, int len) {
|
|
flowCandConsCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()]
|
|
}
|
|
|
|
/**
|
|
* Conceptually a list of `TypedContent`s followed by a `Type`, but only the first two
|
|
* elements of the list and its length are tracked. If data flows from a source to
|
|
* a given node with a given `AccessPath`, this indicates the sequence of
|
|
* dereference operations needed to get from the value in the node to the
|
|
* tracked object. The final type indicates the type of the tracked object.
|
|
*/
|
|
abstract private class AccessPath extends TAccessPath {
|
|
abstract string toString();
|
|
|
|
abstract TypedContent getHead();
|
|
|
|
abstract int len();
|
|
|
|
abstract DataFlowType getType();
|
|
|
|
abstract AccessPathFront getFront();
|
|
|
|
/**
|
|
* Holds if this access path has `head` at the front and may be followed by `tail`.
|
|
*/
|
|
abstract predicate pop(TypedContent head, AccessPath tail);
|
|
}
|
|
|
|
private class AccessPathNil extends AccessPath, TNil {
|
|
private DataFlowType t;
|
|
|
|
AccessPathNil() { this = TNil(t) }
|
|
|
|
override string toString() { result = concat(": " + ppReprType(t)) }
|
|
|
|
override TypedContent getHead() { none() }
|
|
|
|
override int len() { result = 0 }
|
|
|
|
override DataFlowType getType() { result = t }
|
|
|
|
override AccessPathFront getFront() { result = TFrontNil(t) }
|
|
|
|
override predicate pop(TypedContent head, AccessPath tail) { none() }
|
|
}
|
|
|
|
abstract private class AccessPathCons extends AccessPath { }
|
|
|
|
private class AccessPathConsNil extends AccessPathCons, TConsNil {
|
|
private TypedContent tc;
|
|
private DataFlowType t;
|
|
|
|
AccessPathConsNil() { this = TConsNil(tc, t) }
|
|
|
|
override string toString() {
|
|
// The `concat` becomes "" if `ppReprType` has no result.
|
|
result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t))
|
|
}
|
|
|
|
override TypedContent getHead() { result = tc }
|
|
|
|
override int len() { result = 1 }
|
|
|
|
override DataFlowType getType() { result = tc.getContainerType() }
|
|
|
|
override AccessPathFront getFront() { result = TFrontHead(tc) }
|
|
|
|
override predicate pop(TypedContent head, AccessPath tail) { head = tc and tail = TNil(t) }
|
|
}
|
|
|
|
private class AccessPathConsCons extends AccessPathCons, TConsCons {
|
|
private TypedContent tc1;
|
|
private TypedContent tc2;
|
|
private int len;
|
|
|
|
AccessPathConsCons() { this = TConsCons(tc1, tc2, len) }
|
|
|
|
override string toString() {
|
|
if len = 2
|
|
then result = "[" + tc1.toString() + ", " + tc2.toString() + "]"
|
|
else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]"
|
|
}
|
|
|
|
override TypedContent getHead() { result = tc1 }
|
|
|
|
override int len() { result = len }
|
|
|
|
override DataFlowType getType() { result = tc1.getContainerType() }
|
|
|
|
override AccessPathFront getFront() { result = TFrontHead(tc1) }
|
|
|
|
override predicate pop(TypedContent head, AccessPath tail) {
|
|
head = tc1 and
|
|
(
|
|
tail = TConsCons(tc2, _, len - 1)
|
|
or
|
|
len = 2 and
|
|
tail = TConsNil(tc2, _)
|
|
)
|
|
}
|
|
}
|
|
|
|
/** Gets the access path obtained by popping `tc` from `ap`, if any. */
|
|
private AccessPath pop(TypedContent tc, AccessPath ap) { ap.pop(tc, result) }
|
|
|
|
/** Gets the access path obtained by pushing `tc` onto `ap`. */
|
|
private AccessPath push(TypedContent tc, AccessPath ap) { ap = pop(tc, result) }
|
|
|
|
private newtype TAccessPathOption =
|
|
TAccessPathNone() or
|
|
TAccessPathSome(AccessPath ap)
|
|
|
|
private class AccessPathOption extends TAccessPathOption {
|
|
string toString() {
|
|
this = TAccessPathNone() and result = "<none>"
|
|
or
|
|
this = TAccessPathSome(any(AccessPath ap | result = ap.toString()))
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Holds if `node` is reachable with access path `ap` from a source in
|
|
* the configuration `config`.
|
|
*
|
|
* The call context `cc` records whether the node is reached through an
|
|
* argument in a call, and if so, `argAp` records the access path of that
|
|
* argument.
|
|
*/
|
|
private predicate flowFwd(
|
|
Node node, CallContext cc, AccessPathOption argAp, AccessPathFront apf, AccessPath ap,
|
|
Configuration config
|
|
) {
|
|
flowFwd0(node, cc, argAp, apf, ap, config) and
|
|
flowCand(node, _, _, apf, config)
|
|
}
|
|
|
|
private predicate flowFwd0(
|
|
Node node, CallContext cc, AccessPathOption argAp, AccessPathFront apf, AccessPath ap,
|
|
Configuration config
|
|
) {
|
|
flowCand(node, _, _, _, config) and
|
|
config.isSource(node) and
|
|
cc instanceof CallContextAny and
|
|
argAp = TAccessPathNone() and
|
|
ap = TNil(getNodeType(node)) and
|
|
apf = ap.(AccessPathNil).getFront()
|
|
or
|
|
flowCand(node, _, _, _, unbind(config)) and
|
|
(
|
|
exists(Node mid, LocalCallContext localCC |
|
|
flowFwdLocalEntry(mid, cc, argAp, apf, ap, localCC, config) and
|
|
localFlowBigStep(mid, node, true, _, config, localCC)
|
|
)
|
|
or
|
|
exists(Node mid, AccessPathNil nil, LocalCallContext localCC |
|
|
flowFwdLocalEntry(mid, cc, argAp, _, nil, localCC, config) and
|
|
localFlowBigStep(mid, node, false, apf, config, localCC) and
|
|
apf = ap.(AccessPathNil).getFront()
|
|
)
|
|
or
|
|
exists(Node mid |
|
|
flowFwd(mid, _, _, apf, ap, config) and
|
|
jumpStep(mid, node, config) and
|
|
cc instanceof CallContextAny and
|
|
argAp = TAccessPathNone()
|
|
)
|
|
or
|
|
exists(Node mid, AccessPathNil nil |
|
|
flowFwd(mid, _, _, _, nil, config) and
|
|
additionalJumpStep(mid, node, config) and
|
|
cc instanceof CallContextAny and
|
|
argAp = TAccessPathNone() and
|
|
ap = TNil(getNodeType(node)) and
|
|
apf = ap.(AccessPathNil).getFront()
|
|
)
|
|
)
|
|
or
|
|
// store
|
|
exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, ap), apf, cc, argAp, config))
|
|
or
|
|
// read
|
|
exists(TypedContent tc |
|
|
flowFwdRead(node, _, push(tc, ap), apf, cc, argAp, config) and
|
|
flowFwdConsCand(tc, apf, ap, config)
|
|
)
|
|
or
|
|
// flow into a callable
|
|
flowFwdIn(_, node, _, cc, _, apf, ap, config) and
|
|
if flowCand(node, true, _, apf, config)
|
|
then argAp = TAccessPathSome(ap)
|
|
else argAp = TAccessPathNone()
|
|
or
|
|
// flow out of a callable
|
|
exists(DataFlowCall call |
|
|
exists(DataFlowCallable c |
|
|
flowFwdOut(call, node, any(CallContextNoCall innercc), c, argAp, apf, ap, config) and
|
|
if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext()
|
|
)
|
|
or
|
|
exists(AccessPath argAp0 |
|
|
flowFwdOutFromArg(call, node, argAp0, apf, ap, config) and
|
|
flowFwdIsEntered(call, cc, argAp, argAp0, config)
|
|
)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowFwdLocalEntry(
|
|
Node node, CallContext cc, AccessPathOption argAp, AccessPathFront apf, AccessPath ap,
|
|
LocalCallContext localCC, Configuration config
|
|
) {
|
|
flowFwd(node, cc, argAp, apf, ap, config) and
|
|
localFlowEntry(node, config) and
|
|
localCC = getLocalCallContext(cc, node.getEnclosingCallable())
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowFwdStore(
|
|
Node node, TypedContent tc, AccessPath ap0, AccessPathFront apf, CallContext cc,
|
|
AccessPathOption argAp, Configuration config
|
|
) {
|
|
exists(Node mid, AccessPathFront apf0 |
|
|
flowFwd(mid, cc, argAp, apf0, ap0, config) and
|
|
flowFwdStore0(mid, tc, node, apf0, apf, config)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate storeCand(
|
|
Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf,
|
|
Configuration config
|
|
) {
|
|
storeCand2(mid, tc, node, _, config) and
|
|
flowCand(mid, _, _, apf0, config) and
|
|
apf.headUsesContent(tc)
|
|
}
|
|
|
|
pragma[noinline]
|
|
private predicate flowFwdStore0(
|
|
Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf,
|
|
Configuration config
|
|
) {
|
|
storeCand(mid, tc, node, apf0, apf, config) and
|
|
flowCandConsCand(tc, apf0, config) and
|
|
flowCand(node, _, _, apf, unbind(config))
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowFwdRead0(
|
|
Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPath ap0, Node node2, CallContext cc,
|
|
AccessPathOption argAp, Configuration config
|
|
) {
|
|
flowFwd(node1, cc, argAp, apf0, ap0, config) and
|
|
readCandFwd(node1, tc, apf0, node2, config)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowFwdRead(
|
|
Node node, AccessPathFrontHead apf0, AccessPath ap0, AccessPathFront apf, CallContext cc,
|
|
AccessPathOption argAp, Configuration config
|
|
) {
|
|
exists(Node mid, TypedContent tc |
|
|
flowFwdRead0(mid, tc, apf0, ap0, node, cc, argAp, config) and
|
|
flowCand(node, _, _, apf, unbind(config)) and
|
|
flowCandConsCand(tc, apf, unbind(config))
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowFwdConsCand(
|
|
TypedContent tc, AccessPathFront apf, AccessPath ap, Configuration config
|
|
) {
|
|
exists(Node n |
|
|
flowFwd(n, _, _, apf, ap, config) and
|
|
flowFwdStore0(n, tc, _, apf, _, config)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowFwdIn(
|
|
DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc,
|
|
AccessPathOption argAp, AccessPathFront apf, AccessPath ap, Configuration config
|
|
) {
|
|
exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c |
|
|
flowFwd(arg, outercc, argAp, apf, ap, config) and
|
|
flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and
|
|
c = p.getEnclosingCallable() and
|
|
c = resolveCall(call, outercc) and
|
|
flowCand(p, _, _, _, unbind(config)) and
|
|
if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall()
|
|
|
|
|
ap instanceof AccessPathNil or allowsFieldFlow = true
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowFwdOut(
|
|
DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc,
|
|
AccessPathOption argAp, AccessPathFront apf, AccessPath ap, Configuration config
|
|
) {
|
|
exists(ReturnNodeExt ret, boolean allowsFieldFlow |
|
|
flowFwd(ret, innercc, argAp, apf, ap, config) and
|
|
flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and
|
|
innerc = ret.getEnclosingCallable() and
|
|
flowCand(node, _, _, _, unbind(config)) and
|
|
(
|
|
resolveReturn(innercc, innerc, call)
|
|
or
|
|
innercc.(CallContextCall).matchesCall(call)
|
|
)
|
|
|
|
|
ap instanceof AccessPathNil or allowsFieldFlow = true
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowFwdOutFromArg(
|
|
DataFlowCall call, Node node, AccessPath argAp, AccessPathFront apf, AccessPath ap,
|
|
Configuration config
|
|
) {
|
|
flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathSome(argAp), apf, ap, config)
|
|
}
|
|
|
|
/**
|
|
* Holds if an argument to `call` is reached in the flow covered by `flowFwd`.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate flowFwdIsEntered(
|
|
DataFlowCall call, CallContext cc, AccessPathOption argAp, AccessPath ap, Configuration config
|
|
) {
|
|
exists(ParameterNode p, AccessPathFront apf |
|
|
flowFwdIn(call, p, cc, _, argAp, apf, ap, config) and
|
|
flowCand(p, true, TAccessPathFrontSome(_), apf, config)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `node` with access path `ap` is part of a path from a source to
|
|
* a sink in the configuration `config`.
|
|
*
|
|
* The Boolean `toReturn` records whether the node must be returned from
|
|
* the enclosing callable in order to reach a sink, and if so, `returnAp`
|
|
* records the access path of the returned value.
|
|
*/
|
|
private predicate flow(
|
|
Node node, boolean toReturn, AccessPathOption returnAp, AccessPath ap, Configuration config
|
|
) {
|
|
flow0(node, toReturn, returnAp, ap, config) and
|
|
flowFwd(node, _, _, _, ap, config)
|
|
}
|
|
|
|
private predicate flow0(
|
|
Node node, boolean toReturn, AccessPathOption returnAp, AccessPath ap, Configuration config
|
|
) {
|
|
flowFwd(node, _, _, _, ap, config) and
|
|
config.isSink(node) and
|
|
toReturn = false and
|
|
returnAp = TAccessPathNone() and
|
|
ap instanceof AccessPathNil
|
|
or
|
|
exists(Node mid |
|
|
localFlowBigStep(node, mid, true, _, config, _) and
|
|
flow(mid, toReturn, returnAp, ap, config)
|
|
)
|
|
or
|
|
exists(Node mid, AccessPathNil nil |
|
|
flowFwd(node, _, _, _, ap, config) and
|
|
localFlowBigStep(node, mid, false, _, config, _) and
|
|
flow(mid, toReturn, returnAp, nil, config) and
|
|
ap instanceof AccessPathNil
|
|
)
|
|
or
|
|
exists(Node mid |
|
|
jumpStep(node, mid, config) and
|
|
flow(mid, _, _, ap, config) and
|
|
toReturn = false and
|
|
returnAp = TAccessPathNone()
|
|
)
|
|
or
|
|
exists(Node mid, AccessPathNil nil |
|
|
flowFwd(node, _, _, _, ap, config) and
|
|
additionalJumpStep(node, mid, config) and
|
|
flow(mid, _, _, nil, config) and
|
|
toReturn = false and
|
|
returnAp = TAccessPathNone() and
|
|
ap instanceof AccessPathNil
|
|
)
|
|
or
|
|
// store
|
|
exists(TypedContent tc |
|
|
flowStore(tc, node, toReturn, returnAp, ap, config) and
|
|
flowConsCand(tc, ap, config)
|
|
)
|
|
or
|
|
// read
|
|
exists(Node mid, AccessPath ap0 |
|
|
readFlowFwd(node, _, mid, ap, ap0, config) and
|
|
flow(mid, toReturn, returnAp, ap0, config)
|
|
)
|
|
or
|
|
// flow into a callable
|
|
exists(DataFlowCall call |
|
|
flowIn(call, node, toReturn, returnAp, ap, config) and
|
|
toReturn = false
|
|
or
|
|
exists(AccessPath returnAp0 |
|
|
flowInToReturn(call, node, returnAp0, ap, config) and
|
|
flowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
|
)
|
|
)
|
|
or
|
|
// flow out of a callable
|
|
flowOut(_, node, _, _, ap, config) and
|
|
toReturn = true and
|
|
if flowFwd(node, any(CallContextCall ccc), TAccessPathSome(_), _, ap, config)
|
|
then returnAp = TAccessPathSome(ap)
|
|
else returnAp = TAccessPathNone()
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate storeFlowFwd(
|
|
Node node1, TypedContent tc, Node node2, AccessPath ap, AccessPath ap0, Configuration config
|
|
) {
|
|
storeCand2(node1, tc, node2, _, config) and
|
|
flowFwdStore(node2, tc, ap, _, _, _, config) and
|
|
ap0 = push(tc, ap)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowStore(
|
|
TypedContent tc, Node node, boolean toReturn, AccessPathOption returnAp, AccessPath ap,
|
|
Configuration config
|
|
) {
|
|
exists(Node mid, AccessPath ap0 |
|
|
storeFlowFwd(node, tc, mid, ap, ap0, config) and
|
|
flow(mid, toReturn, returnAp, ap0, config)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate readFlowFwd(
|
|
Node node1, TypedContent tc, Node node2, AccessPath ap, AccessPath ap0, Configuration config
|
|
) {
|
|
exists(AccessPathFrontHead apf |
|
|
readCandFwd(node1, tc, apf, node2, config) and
|
|
flowFwdRead(node2, apf, ap, _, _, _, config) and
|
|
ap0 = pop(tc, ap) and
|
|
flowFwdConsCand(tc, _, ap0, unbind(config))
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowConsCand(TypedContent tc, AccessPath ap, Configuration config) {
|
|
exists(Node n, Node mid |
|
|
flow(mid, _, _, ap, config) and
|
|
readFlowFwd(n, tc, mid, _, ap, config)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowOut(
|
|
DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathOption returnAp, AccessPath ap,
|
|
Configuration config
|
|
) {
|
|
exists(Node out, boolean allowsFieldFlow |
|
|
flow(out, toReturn, returnAp, ap, config) and
|
|
flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config)
|
|
|
|
|
ap instanceof AccessPathNil or allowsFieldFlow = true
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowIn(
|
|
DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathOption returnAp, AccessPath ap,
|
|
Configuration config
|
|
) {
|
|
exists(ParameterNode p, boolean allowsFieldFlow |
|
|
flow(p, toReturn, returnAp, ap, config) and
|
|
flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config)
|
|
|
|
|
ap instanceof AccessPathNil or allowsFieldFlow = true
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowInToReturn(
|
|
DataFlowCall call, ArgumentNode arg, AccessPath returnAp, AccessPath ap, Configuration config
|
|
) {
|
|
flowIn(call, arg, true, TAccessPathSome(returnAp), ap, config)
|
|
}
|
|
|
|
/**
|
|
* Holds if an output from `call` is reached in the flow covered by `flow`.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate flowIsReturned(
|
|
DataFlowCall call, boolean toReturn, AccessPathOption returnAp, AccessPath ap,
|
|
Configuration config
|
|
) {
|
|
exists(ReturnNodeExt ret, CallContextCall ccc |
|
|
flowOut(call, ret, toReturn, returnAp, ap, config) and
|
|
flowFwd(ret, ccc, TAccessPathSome(_), _, ap, config) and
|
|
ccc.matchesCall(call)
|
|
)
|
|
}
|
|
|
|
bindingset[conf, result]
|
|
private Configuration unbind(Configuration conf) { result >= conf and result <= conf }
|
|
|
|
private predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) }
|
|
|
|
pragma[noinline]
|
|
private predicate parameterFlow(
|
|
ParameterNode p, AccessPath ap, DataFlowCallable c, Configuration config
|
|
) {
|
|
flow(p, true, _, ap, config) and
|
|
c = p.getEnclosingCallable()
|
|
}
|
|
|
|
private newtype TSummaryCtx =
|
|
TSummaryCtxNone() or
|
|
TSummaryCtxSome(ParameterNode p, AccessPath ap) {
|
|
exists(ReturnNodeExt ret, Configuration config, AccessPath ap0 |
|
|
parameterFlow(p, ap, ret.getEnclosingCallable(), config) and
|
|
flow(ret, true, TAccessPathSome(_), ap0, config) and
|
|
flowFwd(ret, any(CallContextCall ccc), TAccessPathSome(ap), _, ap0, config)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A context for generating flow summaries. This represents flow entry through
|
|
* a specific parameter with an access path of a specific shape.
|
|
*
|
|
* Summaries are only created for parameters that may flow through.
|
|
*/
|
|
abstract private class SummaryCtx extends TSummaryCtx {
|
|
abstract string toString();
|
|
}
|
|
|
|
/** A summary context from which no flow summary can be generated. */
|
|
private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone {
|
|
override string toString() { result = "<none>" }
|
|
}
|
|
|
|
/** A summary context from which a flow summary can be generated. */
|
|
private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
|
|
private ParameterNode p;
|
|
private AccessPath ap;
|
|
|
|
SummaryCtxSome() { this = TSummaryCtxSome(p, ap) }
|
|
|
|
int getParameterPos() { p.isParameterOf(_, result) }
|
|
|
|
override string toString() { result = p + ": " + ap }
|
|
|
|
predicate hasLocationInfo(
|
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
|
) {
|
|
p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
|
}
|
|
}
|
|
|
|
private newtype TPathNode =
|
|
TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) {
|
|
// A PathNode is introduced by a source ...
|
|
flow(node, config) and
|
|
config.isSource(node) and
|
|
cc instanceof CallContextAny and
|
|
sc instanceof SummaryCtxNone and
|
|
ap = TNil(getNodeType(node))
|
|
or
|
|
// ... or a step from an existing PathNode to another node.
|
|
exists(PathNodeMid mid |
|
|
pathStep(mid, node, cc, sc, ap) and
|
|
config = mid.getConfiguration() and
|
|
flow(node, _, _, ap, unbind(config))
|
|
)
|
|
} or
|
|
TPathNodeSink(Node node, Configuration config) {
|
|
config.isSink(node) and
|
|
flow(node, unbind(config)) and
|
|
(
|
|
// A sink that is also a source ...
|
|
config.isSource(node)
|
|
or
|
|
// ... or a sink that can be reached from a source
|
|
exists(PathNodeMid mid |
|
|
pathStep(mid, node, _, _, any(AccessPathNil nil)) and
|
|
config = unbind(mid.getConfiguration())
|
|
)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A `Node` augmented with a call context (except for sinks), an access path, and a configuration.
|
|
* Only those `PathNode`s that are reachable from a source are generated.
|
|
*/
|
|
class PathNode extends TPathNode {
|
|
/** Gets a textual representation of this element. */
|
|
string toString() { none() }
|
|
|
|
/**
|
|
* Gets a textual representation of this element, including a textual
|
|
* representation of the call context.
|
|
*/
|
|
string toStringWithContext() { 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()
|
|
}
|
|
|
|
/** Gets the underlying `Node`. */
|
|
Node getNode() { none() }
|
|
|
|
/** Gets the associated configuration. */
|
|
Configuration getConfiguration() { none() }
|
|
|
|
private predicate isHidden() {
|
|
nodeIsHidden(this.getNode()) and
|
|
not this.isSource() and
|
|
not this instanceof PathNodeSink
|
|
}
|
|
|
|
private PathNode getASuccessorIfHidden() {
|
|
this.isHidden() and
|
|
result = this.(PathNodeImpl).getASuccessorImpl()
|
|
}
|
|
|
|
/** Gets a successor of this node, if any. */
|
|
final PathNode getASuccessor() {
|
|
result = this.(PathNodeImpl).getASuccessorImpl().getASuccessorIfHidden*() and
|
|
not this.isHidden() and
|
|
not result.isHidden()
|
|
}
|
|
|
|
/** Holds if this node is a source. */
|
|
predicate isSource() { none() }
|
|
}
|
|
|
|
abstract private class PathNodeImpl extends PathNode {
|
|
abstract PathNode getASuccessorImpl();
|
|
|
|
private string ppAp() {
|
|
this instanceof PathNodeSink and result = ""
|
|
or
|
|
exists(string s | s = this.(PathNodeMid).getAp().toString() |
|
|
if s = "" then result = "" else result = " " + s
|
|
)
|
|
}
|
|
|
|
private string ppCtx() {
|
|
this instanceof PathNodeSink and result = ""
|
|
or
|
|
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
|
|
}
|
|
|
|
override string toString() { result = this.getNode().toString() + ppAp() }
|
|
|
|
override string toStringWithContext() { result = this.getNode().toString() + ppAp() + ppCtx() }
|
|
|
|
override predicate hasLocationInfo(
|
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
|
) {
|
|
this.getNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
|
}
|
|
}
|
|
|
|
/** Holds if `n` can reach a sink. */
|
|
private predicate reach(PathNode n) { n instanceof PathNodeSink or reach(n.getASuccessor()) }
|
|
|
|
/** Holds if `n1.getSucc() = n2` and `n2` can reach a sink. */
|
|
private predicate pathSucc(PathNode n1, PathNode n2) { n1.getASuccessor() = n2 and reach(n2) }
|
|
|
|
private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1, n2)
|
|
|
|
/**
|
|
* Provides the query predicates needed to include a graph in a path-problem query.
|
|
*/
|
|
module PathGraph {
|
|
/** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
|
|
query predicate edges(PathNode a, PathNode b) { pathSucc(a, b) }
|
|
|
|
/** Holds if `n` is a node in the graph of data flow path explanations. */
|
|
query predicate nodes(PathNode n, string key, string val) {
|
|
reach(n) and key = "semmle.label" and val = n.toString()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An intermediate flow graph node. This is a triple consisting of a `Node`,
|
|
* a `CallContext`, and a `Configuration`.
|
|
*/
|
|
private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
|
|
Node node;
|
|
CallContext cc;
|
|
SummaryCtx sc;
|
|
AccessPath ap;
|
|
Configuration config;
|
|
|
|
PathNodeMid() { this = TPathNodeMid(node, cc, sc, ap, config) }
|
|
|
|
override Node getNode() { result = node }
|
|
|
|
CallContext getCallContext() { result = cc }
|
|
|
|
SummaryCtx getSummaryCtx() { result = sc }
|
|
|
|
AccessPath getAp() { result = ap }
|
|
|
|
override Configuration getConfiguration() { result = config }
|
|
|
|
private PathNodeMid getSuccMid() {
|
|
pathStep(this, result.getNode(), result.getCallContext(), result.getSummaryCtx(), result.getAp()) and
|
|
result.getConfiguration() = unbind(this.getConfiguration())
|
|
}
|
|
|
|
override PathNodeImpl getASuccessorImpl() {
|
|
// an intermediate step to another intermediate node
|
|
result = getSuccMid()
|
|
or
|
|
// a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
|
|
exists(PathNodeMid mid, PathNodeSink sink |
|
|
mid = getSuccMid() and
|
|
mid.getNode() = sink.getNode() and
|
|
mid.getAp() instanceof AccessPathNil and
|
|
sink.getConfiguration() = unbind(mid.getConfiguration()) and
|
|
result = sink
|
|
)
|
|
}
|
|
|
|
override predicate isSource() {
|
|
config.isSource(node) and
|
|
cc instanceof CallContextAny and
|
|
sc instanceof SummaryCtxNone and
|
|
ap instanceof AccessPathNil
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A flow graph node corresponding to a sink. This is disjoint from the
|
|
* intermediate nodes in order to uniquely correspond to a given sink by
|
|
* excluding the `CallContext`.
|
|
*/
|
|
private class PathNodeSink extends PathNodeImpl, TPathNodeSink {
|
|
Node node;
|
|
Configuration config;
|
|
|
|
PathNodeSink() { this = TPathNodeSink(node, config) }
|
|
|
|
override Node getNode() { result = node }
|
|
|
|
override Configuration getConfiguration() { result = config }
|
|
|
|
override PathNode getASuccessorImpl() { none() }
|
|
|
|
override predicate isSource() { config.isSource(node) }
|
|
}
|
|
|
|
/**
|
|
* Holds if data may flow from `mid` to `node`. The last step in or out of
|
|
* a callable is recorded by `cc`.
|
|
*/
|
|
private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCtx sc, AccessPath ap) {
|
|
exists(AccessPath ap0, Node midnode, Configuration conf, LocalCallContext localCC |
|
|
midnode = mid.getNode() and
|
|
conf = mid.getConfiguration() and
|
|
cc = mid.getCallContext() and
|
|
sc = mid.getSummaryCtx() and
|
|
localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and
|
|
ap0 = mid.getAp()
|
|
|
|
|
localFlowBigStep(midnode, node, true, _, conf, localCC) and
|
|
ap = ap0
|
|
or
|
|
localFlowBigStep(midnode, node, false, ap.getFront(), conf, localCC) and
|
|
ap0 instanceof AccessPathNil
|
|
)
|
|
or
|
|
jumpStep(mid.getNode(), node, mid.getConfiguration()) and
|
|
cc instanceof CallContextAny and
|
|
sc instanceof SummaryCtxNone and
|
|
ap = mid.getAp()
|
|
or
|
|
additionalJumpStep(mid.getNode(), node, mid.getConfiguration()) and
|
|
cc instanceof CallContextAny and
|
|
sc instanceof SummaryCtxNone and
|
|
mid.getAp() instanceof AccessPathNil and
|
|
ap = TNil(getNodeType(node))
|
|
or
|
|
exists(TypedContent tc | pathStoreStep(mid, node, pop(tc, ap), tc, cc)) and
|
|
sc = mid.getSummaryCtx()
|
|
or
|
|
exists(TypedContent tc | pathReadStep(mid, node, push(tc, ap), tc, cc)) and
|
|
sc = mid.getSummaryCtx()
|
|
or
|
|
pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
|
|
or
|
|
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
|
|
or
|
|
pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx()
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) {
|
|
readCandFwd(node1, tc, _, node2, config) and
|
|
flow(node2, config)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate pathReadStep(
|
|
PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc
|
|
) {
|
|
ap0 = mid.getAp() and
|
|
readCand(mid.getNode(), tc, node, mid.getConfiguration()) and
|
|
cc = mid.getCallContext()
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) {
|
|
storeCand2(node1, tc, node2, _, config) and
|
|
flow(node2, config)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate pathStoreStep(
|
|
PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc
|
|
) {
|
|
ap0 = mid.getAp() and
|
|
storeCand(mid.getNode(), tc, node, mid.getConfiguration()) and
|
|
cc = mid.getCallContext()
|
|
}
|
|
|
|
private predicate pathOutOfCallable0(
|
|
PathNodeMid mid, ReturnPosition pos, CallContext innercc, AccessPath ap, Configuration config
|
|
) {
|
|
pos = getReturnPosition(mid.getNode()) and
|
|
innercc = mid.getCallContext() and
|
|
innercc instanceof CallContextNoCall and
|
|
ap = mid.getAp() and
|
|
config = mid.getConfiguration()
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate pathOutOfCallable1(
|
|
PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, AccessPath ap,
|
|
Configuration config
|
|
) {
|
|
exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc |
|
|
pathOutOfCallable0(mid, pos, innercc, ap, config) and
|
|
c = pos.getCallable() and
|
|
kind = pos.getKind() and
|
|
resolveReturn(innercc, c, call)
|
|
|
|
|
if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext()
|
|
)
|
|
}
|
|
|
|
pragma[noinline]
|
|
private Node getAnOutNodeFlow(
|
|
ReturnKindExt kind, DataFlowCall call, AccessPath ap, Configuration config
|
|
) {
|
|
result = kind.getAnOutNode(call) and
|
|
flow(result, _, _, ap, config)
|
|
}
|
|
|
|
/**
|
|
* Holds if data may flow from `mid` to `out`. The last step of this path
|
|
* is a return from a callable and is recorded by `cc`, if needed.
|
|
*/
|
|
pragma[noinline]
|
|
private predicate pathOutOfCallable(PathNodeMid mid, Node out, CallContext cc) {
|
|
exists(ReturnKindExt kind, DataFlowCall call, AccessPath ap, Configuration config |
|
|
pathOutOfCallable1(mid, call, kind, cc, ap, config)
|
|
|
|
|
out = getAnOutNodeFlow(kind, call, ap, config)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`.
|
|
*/
|
|
pragma[noinline]
|
|
private predicate pathIntoArg(
|
|
PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap
|
|
) {
|
|
exists(ArgumentNode arg |
|
|
arg = mid.getNode() and
|
|
cc = mid.getCallContext() and
|
|
arg.argumentOf(call, i) and
|
|
ap = mid.getAp()
|
|
)
|
|
}
|
|
|
|
pragma[noinline]
|
|
private predicate parameterCand(
|
|
DataFlowCallable callable, int i, AccessPath ap, Configuration config
|
|
) {
|
|
exists(ParameterNode p |
|
|
flow(p, _, _, ap, config) and
|
|
p.isParameterOf(callable, i)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate pathIntoCallable0(
|
|
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
|
|
AccessPath ap
|
|
) {
|
|
pathIntoArg(mid, i, outercc, call, ap) and
|
|
callable = resolveCall(call, outercc) and
|
|
parameterCand(callable, any(int j | j <= i and j >= i), ap, mid.getConfiguration())
|
|
}
|
|
|
|
/**
|
|
* Holds if data may flow from `mid` to `p` through `call`. The contexts
|
|
* before and after entering the callable are `outercc` and `innercc`,
|
|
* respectively.
|
|
*/
|
|
private predicate pathIntoCallable(
|
|
PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
|
|
DataFlowCall call
|
|
) {
|
|
exists(int i, DataFlowCallable callable, AccessPath ap |
|
|
pathIntoCallable0(mid, callable, i, outercc, call, ap) and
|
|
p.isParameterOf(callable, i) and
|
|
(
|
|
sc = TSummaryCtxSome(p, ap)
|
|
or
|
|
not exists(TSummaryCtxSome(p, ap)) and
|
|
sc = TSummaryCtxNone()
|
|
)
|
|
|
|
|
if recordDataFlowCallSite(call, callable)
|
|
then innercc = TSpecificCall(call)
|
|
else innercc = TSomeCall()
|
|
)
|
|
}
|
|
|
|
/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */
|
|
pragma[nomagic]
|
|
private predicate paramFlowsThrough(
|
|
ReturnKindExt kind, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, Configuration config
|
|
) {
|
|
exists(PathNodeMid mid, ReturnNodeExt ret, int pos |
|
|
mid.getNode() = ret and
|
|
kind = ret.getKind() and
|
|
cc = mid.getCallContext() and
|
|
sc = mid.getSummaryCtx() and
|
|
config = mid.getConfiguration() and
|
|
ap = mid.getAp() and
|
|
pos = sc.getParameterPos() and
|
|
not kind.(ParamUpdateReturnKind).getPosition() = pos
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate pathThroughCallable0(
|
|
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap
|
|
) {
|
|
exists(CallContext innercc, SummaryCtx sc |
|
|
pathIntoCallable(mid, _, cc, innercc, sc, call) and
|
|
paramFlowsThrough(kind, innercc, sc, ap, unbind(mid.getConfiguration()))
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if data may flow from `mid` through a callable to the node `out`.
|
|
* The context `cc` is restored to its value prior to entering the callable.
|
|
*/
|
|
pragma[noinline]
|
|
private predicate pathThroughCallable(PathNodeMid mid, Node out, CallContext cc, AccessPath ap) {
|
|
exists(DataFlowCall call, ReturnKindExt kind |
|
|
pathThroughCallable0(call, mid, kind, cc, ap) and
|
|
out = getAnOutNodeFlow(kind, call, ap, mid.getConfiguration())
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if data can flow (inter-procedurally) from `source` to `sink`.
|
|
*
|
|
* Will only have results if `configuration` has non-empty sources and
|
|
* sinks.
|
|
*/
|
|
private predicate flowsTo(
|
|
PathNode flowsource, PathNodeSink flowsink, Node source, Node sink, Configuration configuration
|
|
) {
|
|
flowsource.isSource() and
|
|
flowsource.getConfiguration() = configuration and
|
|
flowsource.getNode() = source and
|
|
(flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and
|
|
flowsink.getNode() = sink
|
|
}
|
|
|
|
/**
|
|
* Holds if data can flow (inter-procedurally) from `source` to `sink`.
|
|
*
|
|
* Will only have results if `configuration` has non-empty sources and
|
|
* sinks.
|
|
*/
|
|
predicate flowsTo(Node source, Node sink, Configuration configuration) {
|
|
flowsTo(_, _, source, sink, configuration)
|
|
}
|
|
|
|
private module FlowExploration {
|
|
private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) {
|
|
exists(Node node1, Node node2 |
|
|
jumpStep(node1, node2, config)
|
|
or
|
|
additionalJumpStep(node1, node2, config)
|
|
or
|
|
// flow into callable
|
|
viableParamArg(_, node2, node1)
|
|
or
|
|
// flow out of a callable
|
|
viableReturnPosOut(_, getReturnPosition(node1), node2)
|
|
|
|
|
c1 = node1.getEnclosingCallable() and
|
|
c2 = node2.getEnclosingCallable() and
|
|
c1 != c2
|
|
)
|
|
}
|
|
|
|
private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) {
|
|
exists(Node n | config.isSource(n) and c = n.getEnclosingCallable())
|
|
or
|
|
exists(DataFlowCallable mid |
|
|
interestingCallableSrc(mid, config) and callableStep(mid, c, config)
|
|
)
|
|
}
|
|
|
|
private newtype TCallableExt =
|
|
TCallable(DataFlowCallable c, Configuration config) { interestingCallableSrc(c, config) } or
|
|
TCallableSrc()
|
|
|
|
private predicate callableExtSrc(TCallableSrc src) { any() }
|
|
|
|
private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) {
|
|
exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config |
|
|
callableStep(c1, c2, config) and
|
|
ce1 = TCallable(c1, config) and
|
|
ce2 = TCallable(c2, unbind(config))
|
|
)
|
|
or
|
|
exists(Node n, Configuration config |
|
|
ce1 = TCallableSrc() and
|
|
config.isSource(n) and
|
|
ce2 = TCallable(n.getEnclosingCallable(), config)
|
|
)
|
|
}
|
|
|
|
private int distSrcExt(TCallableExt c) =
|
|
shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result)
|
|
|
|
private int distSrc(DataFlowCallable c, Configuration config) {
|
|
result = distSrcExt(TCallable(c, config)) - 1
|
|
}
|
|
|
|
private newtype TPartialAccessPath =
|
|
TPartialNil(DataFlowType t) or
|
|
TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] }
|
|
|
|
/**
|
|
* Conceptually a list of `TypedContent`s followed by a `Type`, but only the first
|
|
* element of the list and its length are tracked. If data flows from a source to
|
|
* a given node with a given `AccessPath`, this indicates the sequence of
|
|
* dereference operations needed to get from the value in the node to the
|
|
* tracked object. The final type indicates the type of the tracked object.
|
|
*/
|
|
private class PartialAccessPath extends TPartialAccessPath {
|
|
abstract string toString();
|
|
|
|
TypedContent getHead() { this = TPartialCons(result, _) }
|
|
|
|
int len() {
|
|
this = TPartialNil(_) and result = 0
|
|
or
|
|
this = TPartialCons(_, result)
|
|
}
|
|
|
|
DataFlowType getType() {
|
|
this = TPartialNil(result)
|
|
or
|
|
exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType())
|
|
}
|
|
|
|
abstract AccessPathFront getFront();
|
|
}
|
|
|
|
private class PartialAccessPathNil extends PartialAccessPath, TPartialNil {
|
|
override string toString() {
|
|
exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t)))
|
|
}
|
|
|
|
override AccessPathFront getFront() {
|
|
exists(DataFlowType t | this = TPartialNil(t) | result = TFrontNil(t))
|
|
}
|
|
}
|
|
|
|
private class PartialAccessPathCons extends PartialAccessPath, TPartialCons {
|
|
override string toString() {
|
|
exists(TypedContent tc, int len | this = TPartialCons(tc, len) |
|
|
if len = 1
|
|
then result = "[" + tc.toString() + "]"
|
|
else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]"
|
|
)
|
|
}
|
|
|
|
override AccessPathFront getFront() {
|
|
exists(TypedContent tc | this = TPartialCons(tc, _) | result = TFrontHead(tc))
|
|
}
|
|
}
|
|
|
|
private newtype TSummaryCtx1 =
|
|
TSummaryCtx1None() or
|
|
TSummaryCtx1Param(ParameterNode p)
|
|
|
|
private newtype TSummaryCtx2 =
|
|
TSummaryCtx2None() or
|
|
TSummaryCtx2Some(PartialAccessPath ap)
|
|
|
|
private newtype TPartialPathNode =
|
|
TPartialPathNodeMk(
|
|
Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap,
|
|
Configuration config
|
|
) {
|
|
config.isSource(node) and
|
|
cc instanceof CallContextAny and
|
|
sc1 = TSummaryCtx1None() and
|
|
sc2 = TSummaryCtx2None() and
|
|
ap = TPartialNil(getNodeType(node)) and
|
|
not fullBarrier(node, config) and
|
|
exists(config.explorationLimit())
|
|
or
|
|
partialPathNodeMk0(node, cc, sc1, sc2, ap, config) and
|
|
distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit()
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate partialPathNodeMk0(
|
|
Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap,
|
|
Configuration config
|
|
) {
|
|
exists(PartialPathNode mid |
|
|
partialPathStep(mid, node, cc, sc1, sc2, ap, config) and
|
|
not fullBarrier(node, config) and
|
|
if node instanceof CastingNode
|
|
then compatibleTypes(getNodeType(node), ap.getType())
|
|
else any()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A `Node` augmented with a call context, an access path, and a configuration.
|
|
*/
|
|
class PartialPathNode extends TPartialPathNode {
|
|
/** Gets a textual representation of this element. */
|
|
string toString() { result = this.getNode().toString() + this.ppAp() }
|
|
|
|
/**
|
|
* Gets a textual representation of this element, including a textual
|
|
* representation of the call context.
|
|
*/
|
|
string toStringWithContext() { result = this.getNode().toString() + this.ppAp() + this.ppCtx() }
|
|
|
|
/**
|
|
* 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
|
|
) {
|
|
this.getNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
|
}
|
|
|
|
/** Gets the underlying `Node`. */
|
|
Node getNode() { none() }
|
|
|
|
/** Gets the associated configuration. */
|
|
Configuration getConfiguration() { none() }
|
|
|
|
/** Gets a successor of this node, if any. */
|
|
PartialPathNode getASuccessor() { none() }
|
|
|
|
/**
|
|
* Gets the approximate distance to the nearest source measured in number
|
|
* of interprocedural steps.
|
|
*/
|
|
int getSourceDistance() {
|
|
result = distSrc(this.getNode().getEnclosingCallable(), this.getConfiguration())
|
|
}
|
|
|
|
private string ppAp() {
|
|
exists(string s | s = this.(PartialPathNodePriv).getAp().toString() |
|
|
if s = "" then result = "" else result = " " + s
|
|
)
|
|
}
|
|
|
|
private string ppCtx() {
|
|
result = " <" + this.(PartialPathNodePriv).getCallContext().toString() + ">"
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Provides the query predicates needed to include a graph in a path-problem query.
|
|
*/
|
|
module PartialPathGraph {
|
|
/** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
|
|
query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b }
|
|
}
|
|
|
|
private class PartialPathNodePriv extends PartialPathNode {
|
|
Node node;
|
|
CallContext cc;
|
|
TSummaryCtx1 sc1;
|
|
TSummaryCtx2 sc2;
|
|
PartialAccessPath ap;
|
|
Configuration config;
|
|
|
|
PartialPathNodePriv() { this = TPartialPathNodeMk(node, cc, sc1, sc2, ap, config) }
|
|
|
|
override Node getNode() { result = node }
|
|
|
|
CallContext getCallContext() { result = cc }
|
|
|
|
TSummaryCtx1 getSummaryCtx1() { result = sc1 }
|
|
|
|
TSummaryCtx2 getSummaryCtx2() { result = sc2 }
|
|
|
|
PartialAccessPath getAp() { result = ap }
|
|
|
|
override Configuration getConfiguration() { result = config }
|
|
|
|
override PartialPathNodePriv getASuccessor() {
|
|
partialPathStep(this, result.getNode(), result.getCallContext(), result.getSummaryCtx1(),
|
|
result.getSummaryCtx2(), result.getAp(), result.getConfiguration())
|
|
}
|
|
}
|
|
|
|
private predicate partialPathStep(
|
|
PartialPathNodePriv mid, Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2,
|
|
PartialAccessPath ap, Configuration config
|
|
) {
|
|
not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and
|
|
(
|
|
localFlowStep(mid.getNode(), node, config) and
|
|
cc = mid.getCallContext() and
|
|
sc1 = mid.getSummaryCtx1() and
|
|
sc2 = mid.getSummaryCtx2() and
|
|
ap = mid.getAp() and
|
|
config = mid.getConfiguration()
|
|
or
|
|
additionalLocalFlowStep(mid.getNode(), node, config) and
|
|
cc = mid.getCallContext() and
|
|
sc1 = mid.getSummaryCtx1() and
|
|
sc2 = mid.getSummaryCtx2() and
|
|
mid.getAp() instanceof PartialAccessPathNil and
|
|
ap = TPartialNil(getNodeType(node)) and
|
|
config = mid.getConfiguration()
|
|
)
|
|
or
|
|
jumpStep(mid.getNode(), node, config) and
|
|
cc instanceof CallContextAny and
|
|
sc1 = TSummaryCtx1None() and
|
|
sc2 = TSummaryCtx2None() and
|
|
ap = mid.getAp() and
|
|
config = mid.getConfiguration()
|
|
or
|
|
additionalJumpStep(mid.getNode(), node, config) and
|
|
cc instanceof CallContextAny and
|
|
sc1 = TSummaryCtx1None() and
|
|
sc2 = TSummaryCtx2None() and
|
|
mid.getAp() instanceof PartialAccessPathNil and
|
|
ap = TPartialNil(getNodeType(node)) and
|
|
config = mid.getConfiguration()
|
|
or
|
|
partialPathStoreStep(mid, _, _, node, ap) and
|
|
cc = mid.getCallContext() and
|
|
sc1 = mid.getSummaryCtx1() and
|
|
sc2 = mid.getSummaryCtx2() and
|
|
config = mid.getConfiguration()
|
|
or
|
|
exists(PartialAccessPath ap0, TypedContent tc |
|
|
partialPathReadStep(mid, ap0, tc, node, cc, config) and
|
|
sc1 = mid.getSummaryCtx1() and
|
|
sc2 = mid.getSummaryCtx2() and
|
|
apConsFwd(ap, tc, ap0, config) and
|
|
compatibleTypes(ap.getType(), getNodeType(node))
|
|
)
|
|
or
|
|
partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config)
|
|
or
|
|
partialPathOutOfCallable(mid, node, cc, ap, config) and
|
|
sc1 = TSummaryCtx1None() and
|
|
sc2 = TSummaryCtx2None()
|
|
or
|
|
partialPathThroughCallable(mid, node, cc, ap, config) and
|
|
sc1 = mid.getSummaryCtx1() and
|
|
sc2 = mid.getSummaryCtx2()
|
|
}
|
|
|
|
bindingset[result, i]
|
|
private int unbindInt(int i) { i <= result and i >= result }
|
|
|
|
pragma[inline]
|
|
private predicate partialPathStoreStep(
|
|
PartialPathNodePriv mid, PartialAccessPath ap1, TypedContent tc, Node node,
|
|
PartialAccessPath ap2
|
|
) {
|
|
exists(Node midNode, DataFlowType contentType |
|
|
midNode = mid.getNode() and
|
|
ap1 = mid.getAp() and
|
|
store(midNode, tc, node, contentType) and
|
|
ap2.getHead() = tc and
|
|
ap2.len() = unbindInt(ap1.len() + 1) and
|
|
compatibleTypes(ap1.getType(), contentType)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate apConsFwd(
|
|
PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config
|
|
) {
|
|
exists(PartialPathNodePriv mid |
|
|
partialPathStoreStep(mid, ap1, tc, _, ap2) and
|
|
config = mid.getConfiguration()
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate partialPathReadStep(
|
|
PartialPathNodePriv mid, PartialAccessPath ap, TypedContent tc, Node node, CallContext cc,
|
|
Configuration config
|
|
) {
|
|
exists(Node midNode |
|
|
midNode = mid.getNode() and
|
|
ap = mid.getAp() and
|
|
read(midNode, tc.getContent(), node) and
|
|
ap.getHead() = tc and
|
|
config = mid.getConfiguration() and
|
|
cc = mid.getCallContext()
|
|
)
|
|
}
|
|
|
|
private predicate partialPathOutOfCallable0(
|
|
PartialPathNodePriv mid, ReturnPosition pos, CallContext innercc, PartialAccessPath ap,
|
|
Configuration config
|
|
) {
|
|
pos = getReturnPosition(mid.getNode()) and
|
|
innercc = mid.getCallContext() and
|
|
innercc instanceof CallContextNoCall and
|
|
ap = mid.getAp() and
|
|
config = mid.getConfiguration()
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate partialPathOutOfCallable1(
|
|
PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc,
|
|
PartialAccessPath ap, Configuration config
|
|
) {
|
|
exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc |
|
|
partialPathOutOfCallable0(mid, pos, innercc, ap, config) and
|
|
c = pos.getCallable() and
|
|
kind = pos.getKind() and
|
|
resolveReturn(innercc, c, call)
|
|
|
|
|
if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext()
|
|
)
|
|
}
|
|
|
|
private predicate partialPathOutOfCallable(
|
|
PartialPathNodePriv mid, Node out, CallContext cc, PartialAccessPath ap, Configuration config
|
|
) {
|
|
exists(ReturnKindExt kind, DataFlowCall call |
|
|
partialPathOutOfCallable1(mid, call, kind, cc, ap, config)
|
|
|
|
|
out = kind.getAnOutNode(call)
|
|
)
|
|
}
|
|
|
|
pragma[noinline]
|
|
private predicate partialPathIntoArg(
|
|
PartialPathNodePriv mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap,
|
|
Configuration config
|
|
) {
|
|
exists(ArgumentNode arg |
|
|
arg = mid.getNode() and
|
|
cc = mid.getCallContext() and
|
|
arg.argumentOf(call, i) and
|
|
ap = mid.getAp() and
|
|
config = mid.getConfiguration()
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate partialPathIntoCallable0(
|
|
PartialPathNodePriv mid, DataFlowCallable callable, int i, CallContext outercc,
|
|
DataFlowCall call, PartialAccessPath ap, Configuration config
|
|
) {
|
|
partialPathIntoArg(mid, i, outercc, call, ap, config) and
|
|
callable = resolveCall(call, outercc)
|
|
}
|
|
|
|
private predicate partialPathIntoCallable(
|
|
PartialPathNodePriv mid, ParameterNode p, CallContext outercc, CallContextCall innercc,
|
|
TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap,
|
|
Configuration config
|
|
) {
|
|
exists(int i, DataFlowCallable callable |
|
|
partialPathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
|
|
p.isParameterOf(callable, i) and
|
|
sc1 = TSummaryCtx1Param(p) and
|
|
sc2 = TSummaryCtx2Some(ap)
|
|
|
|
|
if recordDataFlowCallSite(call, callable)
|
|
then innercc = TSpecificCall(call)
|
|
else innercc = TSomeCall()
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate paramFlowsThroughInPartialPath(
|
|
ReturnKindExt kind, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2,
|
|
PartialAccessPath ap, Configuration config
|
|
) {
|
|
exists(PartialPathNodePriv mid, ReturnNodeExt ret |
|
|
mid.getNode() = ret and
|
|
kind = ret.getKind() and
|
|
cc = mid.getCallContext() and
|
|
sc1 = mid.getSummaryCtx1() and
|
|
sc2 = mid.getSummaryCtx2() and
|
|
config = mid.getConfiguration() and
|
|
ap = mid.getAp()
|
|
)
|
|
}
|
|
|
|
pragma[noinline]
|
|
private predicate partialPathThroughCallable0(
|
|
DataFlowCall call, PartialPathNodePriv mid, ReturnKindExt kind, CallContext cc,
|
|
PartialAccessPath ap, Configuration config
|
|
) {
|
|
exists(ParameterNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 |
|
|
partialPathIntoCallable(mid, p, cc, innercc, sc1, sc2, call, _, config) and
|
|
paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config)
|
|
)
|
|
}
|
|
|
|
private predicate partialPathThroughCallable(
|
|
PartialPathNodePriv mid, Node out, CallContext cc, PartialAccessPath ap, Configuration config
|
|
) {
|
|
exists(DataFlowCall call, ReturnKindExt kind |
|
|
partialPathThroughCallable0(call, mid, kind, cc, ap, config) and
|
|
out = kind.getAnOutNode(call)
|
|
)
|
|
}
|
|
}
|
|
|
|
import FlowExploration
|
|
|
|
private predicate partialFlow(
|
|
PartialPathNode source, PartialPathNode node, Configuration configuration
|
|
) {
|
|
source.getConfiguration() = configuration and
|
|
configuration.isSource(source.getNode()) and
|
|
node = source.getASuccessor+()
|
|
}
|