mirror of
https://github.com/github/codeql.git
synced 2025-12-17 01:03:14 +01:00
4587 lines
151 KiB
Plaintext
4587 lines
151 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
|
|
import DataFlowImplCommonPublic
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
predicate isSource(Node source) { none() }
|
|
|
|
/**
|
|
* Holds if `source` is a relevant data flow source with the given initial
|
|
* `state`.
|
|
*/
|
|
predicate isSource(Node source, FlowState state) { none() }
|
|
|
|
/**
|
|
* Holds if `sink` is a relevant data flow sink.
|
|
*/
|
|
predicate isSink(Node sink) { none() }
|
|
|
|
/**
|
|
* Holds if `sink` is a relevant data flow sink accepting `state`.
|
|
*/
|
|
predicate isSink(Node source, FlowState state) { none() }
|
|
|
|
/**
|
|
* 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 through `node` is prohibited when the flow state is
|
|
* `state`.
|
|
*/
|
|
predicate isBarrier(Node node, FlowState state) { 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() }
|
|
|
|
/**
|
|
* DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead.
|
|
*
|
|
* Holds if data flow through nodes guarded by `guard` is prohibited.
|
|
*/
|
|
deprecated predicate isBarrierGuard(BarrierGuard guard) { none() }
|
|
|
|
/**
|
|
* DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead.
|
|
*
|
|
* Holds if data flow through nodes guarded by `guard` is prohibited when
|
|
* the flow state is `state`
|
|
*/
|
|
deprecated predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() }
|
|
|
|
/**
|
|
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
|
*/
|
|
predicate isAdditionalFlowStep(Node node1, Node node2) { none() }
|
|
|
|
/**
|
|
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
|
* This step is only applicable in `state1` and updates the flow state to `state2`.
|
|
*/
|
|
predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) {
|
|
none()
|
|
}
|
|
|
|
/**
|
|
* Holds if an arbitrary number of implicit read steps of content `c` may be
|
|
* taken at `node`.
|
|
*/
|
|
predicate allowImplicitRead(Node node, ContentSet c) { none() }
|
|
|
|
/**
|
|
* Gets the virtual dispatch branching limit when calculating field flow.
|
|
* This can be overridden to a smaller value to improve performance (a
|
|
* value of 0 disables field flow), or a larger value to get more results.
|
|
*/
|
|
int fieldFlowBranchLimit() { result = 2 }
|
|
|
|
/**
|
|
* Gets a data flow configuration feature to add restrictions to the set of
|
|
* valid flow paths.
|
|
*
|
|
* - `FeatureHasSourceCallContext`:
|
|
* Assume that sources have some existing call context to disallow
|
|
* conflicting return-flow directly following the source.
|
|
* - `FeatureHasSinkCallContext`:
|
|
* Assume that sinks have some existing call context to disallow
|
|
* conflicting argument-to-parameter flow directly preceding the sink.
|
|
* - `FeatureEqualSourceSinkCallContext`:
|
|
* Implies both of the above and additionally ensures that the entire flow
|
|
* path preserves the call context.
|
|
*/
|
|
FlowFeature getAFeature() { none() }
|
|
|
|
/** Holds if sources should be grouped in the result of `hasFlowPath`. */
|
|
predicate sourceGrouping(Node source, string sourceGroup) { none() }
|
|
|
|
/** Holds if sinks should be grouped in the result of `hasFlowPath`. */
|
|
predicate sinkGrouping(Node sink, string sinkGroup) { none() }
|
|
|
|
/**
|
|
* 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) { hasFlowPath(source, sink, this) }
|
|
|
|
/**
|
|
* Holds if data may flow from some source to `sink` for this configuration.
|
|
*/
|
|
predicate hasFlowTo(Node sink) {
|
|
sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode()
|
|
}
|
|
|
|
/**
|
|
* Holds if data may flow from some source to `sink` for this configuration.
|
|
*/
|
|
predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
|
|
|
|
/**
|
|
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
|
|
* measured in approximate number of interprocedural steps.
|
|
*/
|
|
int explorationLimit() { none() }
|
|
|
|
/**
|
|
* Holds if hidden nodes should be included in the data flow graph.
|
|
*
|
|
* This feature should only be used for debugging or when the data flow graph
|
|
* is not visualized (for example in a `path-problem` query).
|
|
*/
|
|
predicate includeHiddenNodes() { 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 data-flow 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()
|
|
}
|
|
|
|
/**
|
|
* Holds if there is a partial data flow path from `node` to `sink`. The
|
|
* approximate distance between `node` and the closest sink is `dist` and
|
|
* is restricted to be less than or equal to `explorationLimit()`. This
|
|
* predicate completely disregards source definitions.
|
|
*
|
|
* This predicate is intended for data-flow exploration and debugging and may
|
|
* perform poorly if the number of sinks 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`.
|
|
*
|
|
* Note that reverse flow has slightly lower precision than the corresponding
|
|
* forward flow, as reverse flow disregards type pruning among other features.
|
|
*/
|
|
final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) {
|
|
revPartialFlow(node, sink, this) and
|
|
dist = node.getSinkDistance()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.isSource(n, _)) < 0
|
|
or
|
|
strictcount(Node n | this.isSink(n)) < 0
|
|
or
|
|
strictcount(Node n | this.isSink(n, _)) < 0
|
|
or
|
|
strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0
|
|
or
|
|
strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, _, n2, _)) < 0
|
|
or
|
|
super.hasFlow(source, sink)
|
|
}
|
|
}
|
|
|
|
private newtype TNodeEx =
|
|
TNodeNormal(Node n) or
|
|
TNodeImplicitRead(Node n, boolean hasRead) {
|
|
any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true]
|
|
}
|
|
|
|
private class NodeEx extends TNodeEx {
|
|
string toString() {
|
|
result = this.asNode().toString()
|
|
or
|
|
exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]")
|
|
}
|
|
|
|
Node asNode() { this = TNodeNormal(result) }
|
|
|
|
predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) }
|
|
|
|
Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) }
|
|
|
|
pragma[nomagic]
|
|
private DataFlowCallable getEnclosingCallable0() {
|
|
nodeEnclosingCallable(this.projectToNode(), result)
|
|
}
|
|
|
|
pragma[inline]
|
|
DataFlowCallable getEnclosingCallable() {
|
|
pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) }
|
|
|
|
pragma[inline]
|
|
DataFlowType getDataFlowType() {
|
|
pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result)
|
|
}
|
|
|
|
predicate hasLocationInfo(
|
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
|
) {
|
|
this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
|
}
|
|
}
|
|
|
|
private class ArgNodeEx extends NodeEx {
|
|
ArgNodeEx() { this.asNode() instanceof ArgNode }
|
|
}
|
|
|
|
private class ParamNodeEx extends NodeEx {
|
|
ParamNodeEx() { this.asNode() instanceof ParamNode }
|
|
|
|
predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
|
|
this.asNode().(ParamNode).isParameterOf(c, pos)
|
|
}
|
|
|
|
ParameterPosition getPosition() { this.isParameterOf(_, result) }
|
|
|
|
predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
|
|
}
|
|
|
|
private class RetNodeEx extends NodeEx {
|
|
RetNodeEx() { this.asNode() instanceof ReturnNodeExt }
|
|
|
|
ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) }
|
|
|
|
ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() }
|
|
}
|
|
|
|
private predicate inBarrier(NodeEx node, Configuration config) {
|
|
exists(Node n |
|
|
node.asNode() = n and
|
|
config.isBarrierIn(n)
|
|
|
|
|
config.isSource(n) or config.isSource(n, _)
|
|
)
|
|
}
|
|
|
|
private predicate outBarrier(NodeEx node, Configuration config) {
|
|
exists(Node n |
|
|
node.asNode() = n and
|
|
config.isBarrierOut(n)
|
|
|
|
|
config.isSink(n) or config.isSink(n, _)
|
|
)
|
|
}
|
|
|
|
/** A bridge class to access the deprecated `isBarrierGuard`. */
|
|
private class BarrierGuardGuardedNodeBridge extends Unit {
|
|
abstract predicate guardedNode(Node n, Configuration config);
|
|
|
|
abstract predicate guardedNode(Node n, FlowState state, Configuration config);
|
|
}
|
|
|
|
private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge {
|
|
deprecated override predicate guardedNode(Node n, Configuration config) {
|
|
exists(BarrierGuard g |
|
|
config.isBarrierGuard(g) and
|
|
n = g.getAGuardedNode()
|
|
)
|
|
}
|
|
|
|
deprecated override predicate guardedNode(Node n, FlowState state, Configuration config) {
|
|
exists(BarrierGuard g |
|
|
config.isBarrierGuard(g, state) and
|
|
n = g.getAGuardedNode()
|
|
)
|
|
}
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate fullBarrier(NodeEx node, Configuration config) {
|
|
exists(Node n | node.asNode() = n |
|
|
config.isBarrier(n)
|
|
or
|
|
config.isBarrierIn(n) and
|
|
not config.isSource(n) and
|
|
not config.isSource(n, _)
|
|
or
|
|
config.isBarrierOut(n) and
|
|
not config.isSink(n) and
|
|
not config.isSink(n, _)
|
|
or
|
|
any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate stateBarrier(NodeEx node, FlowState state, Configuration config) {
|
|
exists(Node n | node.asNode() = n |
|
|
config.isBarrier(n, state)
|
|
or
|
|
any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate sourceNode(NodeEx node, FlowState state, Configuration config) {
|
|
(
|
|
config.isSource(node.asNode()) and state instanceof FlowStateEmpty
|
|
or
|
|
config.isSource(node.asNode(), state)
|
|
) and
|
|
not fullBarrier(node, config) and
|
|
not stateBarrier(node, state, config)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate sinkNode(NodeEx node, FlowState state, Configuration config) {
|
|
(
|
|
config.isSink(node.asNode()) and state instanceof FlowStateEmpty
|
|
or
|
|
config.isSink(node.asNode(), state)
|
|
) and
|
|
not fullBarrier(node, config) and
|
|
not stateBarrier(node, state, config)
|
|
}
|
|
|
|
/** Provides the relevant barriers for a step from `node1` to `node2`. */
|
|
pragma[inline]
|
|
private predicate stepFilter(NodeEx node1, NodeEx node2, Configuration config) {
|
|
not outBarrier(node1, config) and
|
|
not inBarrier(node2, config) and
|
|
not fullBarrier(node1, config) and
|
|
not fullBarrier(node2, config)
|
|
}
|
|
|
|
/**
|
|
* Holds if data can flow in one local step from `node1` to `node2`.
|
|
*/
|
|
private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) {
|
|
exists(Node n1, Node n2 |
|
|
node1.asNode() = n1 and
|
|
node2.asNode() = n2 and
|
|
simpleLocalFlowStepExt(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and
|
|
stepFilter(node1, node2, config)
|
|
)
|
|
or
|
|
exists(Node n |
|
|
config.allowImplicitRead(n, _) and
|
|
node1.asNode() = n and
|
|
node2.isImplicitReadNode(n, false) and
|
|
not fullBarrier(node1, config)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if the additional step from `node1` to `node2` does not jump between callables.
|
|
*/
|
|
private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) {
|
|
exists(Node n1, Node n2 |
|
|
node1.asNode() = n1 and
|
|
node2.asNode() = n2 and
|
|
config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and
|
|
getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and
|
|
stepFilter(node1, node2, config)
|
|
)
|
|
or
|
|
exists(Node n |
|
|
config.allowImplicitRead(n, _) and
|
|
node1.isImplicitReadNode(n, true) and
|
|
node2.asNode() = n and
|
|
not fullBarrier(node2, config)
|
|
)
|
|
}
|
|
|
|
private predicate additionalLocalStateStep(
|
|
NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config
|
|
) {
|
|
exists(Node n1, Node n2 |
|
|
node1.asNode() = n1 and
|
|
node2.asNode() = n2 and
|
|
config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and
|
|
getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and
|
|
stepFilter(node1, node2, config) and
|
|
not stateBarrier(node1, s1, config) and
|
|
not stateBarrier(node2, s2, config)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if data can flow from `node1` to `node2` in a way that discards call contexts.
|
|
*/
|
|
private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
|
|
exists(Node n1, Node n2 |
|
|
node1.asNode() = n1 and
|
|
node2.asNode() = n2 and
|
|
jumpStepCached(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and
|
|
stepFilter(node1, node2, config) and
|
|
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if the additional step from `node1` to `node2` jumps between callables.
|
|
*/
|
|
private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) {
|
|
exists(Node n1, Node n2 |
|
|
node1.asNode() = n1 and
|
|
node2.asNode() = n2 and
|
|
config.isAdditionalFlowStep(pragma[only_bind_into](n1), pragma[only_bind_into](n2)) and
|
|
getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and
|
|
stepFilter(node1, node2, config) and
|
|
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
|
)
|
|
}
|
|
|
|
private predicate additionalJumpStateStep(
|
|
NodeEx node1, FlowState s1, NodeEx node2, FlowState s2, Configuration config
|
|
) {
|
|
exists(Node n1, Node n2 |
|
|
node1.asNode() = n1 and
|
|
node2.asNode() = n2 and
|
|
config.isAdditionalFlowStep(pragma[only_bind_into](n1), s1, pragma[only_bind_into](n2), s2) and
|
|
getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and
|
|
stepFilter(node1, node2, config) and
|
|
not stateBarrier(node1, s1, config) and
|
|
not stateBarrier(node2, s2, config) and
|
|
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate readSet(NodeEx node1, ContentSet c, NodeEx node2, Configuration config) {
|
|
readSet(pragma[only_bind_into](node1.asNode()), c, pragma[only_bind_into](node2.asNode())) and
|
|
stepFilter(node1, node2, config)
|
|
or
|
|
exists(Node n |
|
|
node2.isImplicitReadNode(n, true) and
|
|
node1.isImplicitReadNode(n, _) and
|
|
config.allowImplicitRead(n, c)
|
|
)
|
|
}
|
|
|
|
// inline to reduce fan-out via `getAReadContent`
|
|
bindingset[c]
|
|
private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) {
|
|
exists(ContentSet cs |
|
|
readSet(node1, cs, node2, config) and
|
|
pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent()
|
|
)
|
|
}
|
|
|
|
// inline to reduce fan-out via `getAReadContent`
|
|
bindingset[c]
|
|
private predicate clearsContentEx(NodeEx n, Content c) {
|
|
exists(ContentSet cs |
|
|
clearsContentCached(n.asNode(), cs) and
|
|
pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent()
|
|
)
|
|
}
|
|
|
|
// inline to reduce fan-out via `getAReadContent`
|
|
bindingset[c]
|
|
private predicate expectsContentEx(NodeEx n, Content c) {
|
|
exists(ContentSet cs |
|
|
expectsContentCached(n.asNode(), cs) and
|
|
pragma[only_bind_out](c) = pragma[only_bind_into](cs).getAReadContent()
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate notExpectsContent(NodeEx n) { not expectsContentCached(n.asNode(), _) }
|
|
|
|
pragma[nomagic]
|
|
private predicate hasReadStep(Content c, Configuration config) { read(_, c, _, config) }
|
|
|
|
pragma[nomagic]
|
|
private predicate store(
|
|
NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config
|
|
) {
|
|
store(pragma[only_bind_into](node1.asNode()), tc, pragma[only_bind_into](node2.asNode()),
|
|
contentType) and
|
|
hasReadStep(tc.getContent(), config) and
|
|
stepFilter(node1, node2, config)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) {
|
|
viableReturnPosOut(call, pos, out.asNode())
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) {
|
|
viableParamArg(call, p.asNode(), arg.asNode())
|
|
}
|
|
|
|
/**
|
|
* Holds if field flow should be used for the given configuration.
|
|
*/
|
|
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
|
|
|
|
private predicate hasSourceCallCtx(Configuration config) {
|
|
exists(FlowFeature feature | feature = config.getAFeature() |
|
|
feature instanceof FeatureHasSourceCallContext or
|
|
feature instanceof FeatureEqualSourceSinkCallContext
|
|
)
|
|
}
|
|
|
|
private predicate hasSinkCallCtx(Configuration config) {
|
|
exists(FlowFeature feature | feature = config.getAFeature() |
|
|
feature instanceof FeatureHasSinkCallContext or
|
|
feature instanceof FeatureEqualSourceSinkCallContext
|
|
)
|
|
}
|
|
|
|
private module Stage1 implements StageSig {
|
|
class Ap = Unit;
|
|
|
|
private class Cc = boolean;
|
|
|
|
/* Begin: Stage 1 logic. */
|
|
/**
|
|
* Holds if `node` is reachable from a source in the configuration `config`.
|
|
*
|
|
* The Boolean `cc` records whether the node is reached through an
|
|
* argument in a call.
|
|
*/
|
|
private predicate fwdFlow(NodeEx node, Cc cc, Configuration config) {
|
|
sourceNode(node, _, config) and
|
|
if hasSourceCallCtx(config) then cc = true else cc = false
|
|
or
|
|
exists(NodeEx mid | fwdFlow(mid, cc, config) |
|
|
localFlowStep(mid, node, config) or
|
|
additionalLocalFlowStep(mid, node, config) or
|
|
additionalLocalStateStep(mid, _, node, _, config)
|
|
)
|
|
or
|
|
exists(NodeEx mid | fwdFlow(mid, _, config) and cc = false |
|
|
jumpStep(mid, node, config) or
|
|
additionalJumpStep(mid, node, config) or
|
|
additionalJumpStateStep(mid, _, node, _, config)
|
|
)
|
|
or
|
|
// store
|
|
exists(NodeEx mid |
|
|
useFieldFlow(config) and
|
|
fwdFlow(mid, cc, config) and
|
|
store(mid, _, node, _, config)
|
|
)
|
|
or
|
|
// read
|
|
exists(ContentSet c |
|
|
fwdFlowReadSet(c, node, cc, config) and
|
|
fwdFlowConsCandSet(c, _, config)
|
|
)
|
|
or
|
|
// flow into a callable
|
|
exists(NodeEx arg |
|
|
fwdFlow(arg, _, config) and
|
|
viableParamArgEx(_, node, arg) and
|
|
cc = true and
|
|
not fullBarrier(node, config)
|
|
)
|
|
or
|
|
// flow out of a callable
|
|
exists(DataFlowCall call |
|
|
fwdFlowOut(call, node, false, config) and
|
|
cc = false
|
|
or
|
|
fwdFlowOutFromArg(call, node, config) and
|
|
fwdFlowIsEntered(call, cc, config)
|
|
)
|
|
}
|
|
|
|
private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) }
|
|
|
|
pragma[nomagic]
|
|
private predicate fwdFlowReadSet(ContentSet c, NodeEx node, Cc cc, Configuration config) {
|
|
exists(NodeEx mid |
|
|
fwdFlow(mid, cc, config) and
|
|
readSet(mid, c, node, config)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `c` is the target of a store in the flow covered by `fwdFlow`.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate fwdFlowConsCand(Content c, Configuration config) {
|
|
exists(NodeEx mid, NodeEx node, TypedContent tc |
|
|
not fullBarrier(node, config) and
|
|
useFieldFlow(config) and
|
|
fwdFlow(mid, _, config) and
|
|
store(mid, tc, node, _, config) and
|
|
c = tc.getContent()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `cs` may be interpreted in a read as the target of some store
|
|
* into `c`, in the flow covered by `fwdFlow`.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate fwdFlowConsCandSet(ContentSet cs, Content c, Configuration config) {
|
|
fwdFlowConsCand(c, config) and
|
|
c = cs.getAReadContent()
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) {
|
|
exists(RetNodeEx ret |
|
|
fwdFlow(ret, cc, config) and
|
|
ret.getReturnPosition() = pos
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) {
|
|
exists(ReturnPosition pos |
|
|
fwdFlowReturnPosition(pos, cc, config) and
|
|
viableReturnPosOutEx(call, pos, out) and
|
|
not fullBarrier(out, config)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) {
|
|
fwdFlowOut(call, out, true, config)
|
|
}
|
|
|
|
/**
|
|
* Holds if an argument to `call` is reached in the flow covered by `fwdFlow`.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) {
|
|
exists(ArgNodeEx arg |
|
|
fwdFlow(arg, cc, config) and
|
|
viableParamArgEx(call, _, arg)
|
|
)
|
|
}
|
|
|
|
private predicate stateStepFwd(FlowState state1, FlowState state2, Configuration config) {
|
|
exists(NodeEx node1 |
|
|
additionalLocalStateStep(node1, state1, _, state2, config) or
|
|
additionalJumpStateStep(node1, state1, _, state2, config)
|
|
|
|
|
fwdFlow(node1, config)
|
|
)
|
|
}
|
|
|
|
private predicate fwdFlowState(FlowState state, Configuration config) {
|
|
sourceNode(_, state, config)
|
|
or
|
|
exists(FlowState state0 |
|
|
fwdFlowState(state0, config) and
|
|
stateStepFwd(state0, state, config)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* 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 revFlow(NodeEx node, boolean toReturn, Configuration config) {
|
|
revFlow0(node, toReturn, config) and
|
|
fwdFlow(node, config)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
|
|
exists(FlowState state |
|
|
fwdFlow(node, pragma[only_bind_into](config)) and
|
|
sinkNode(node, state, config) and
|
|
fwdFlowState(state, pragma[only_bind_into](config)) and
|
|
if hasSinkCallCtx(config) then toReturn = true else toReturn = false
|
|
)
|
|
or
|
|
exists(NodeEx mid | revFlow(mid, toReturn, config) |
|
|
localFlowStep(node, mid, config) or
|
|
additionalLocalFlowStep(node, mid, config) or
|
|
additionalLocalStateStep(node, _, mid, _, config)
|
|
)
|
|
or
|
|
exists(NodeEx mid | revFlow(mid, _, config) and toReturn = false |
|
|
jumpStep(node, mid, config) or
|
|
additionalJumpStep(node, mid, config) or
|
|
additionalJumpStateStep(node, _, mid, _, config)
|
|
)
|
|
or
|
|
// store
|
|
exists(Content c |
|
|
revFlowStore(c, node, toReturn, config) and
|
|
revFlowConsCand(c, config)
|
|
)
|
|
or
|
|
// read
|
|
exists(NodeEx mid, ContentSet c |
|
|
readSet(node, c, mid, config) and
|
|
fwdFlowConsCandSet(c, _, pragma[only_bind_into](config)) and
|
|
revFlow(mid, toReturn, pragma[only_bind_into](config))
|
|
)
|
|
or
|
|
// flow into a callable
|
|
exists(DataFlowCall call |
|
|
revFlowIn(call, node, false, config) and
|
|
toReturn = false
|
|
or
|
|
revFlowInToReturn(call, node, config) and
|
|
revFlowIsReturned(call, toReturn, config)
|
|
)
|
|
or
|
|
// flow out of a callable
|
|
exists(ReturnPosition pos |
|
|
revFlowOut(pos, config) and
|
|
node.(RetNodeEx).getReturnPosition() = pos and
|
|
toReturn = true
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `c` is the target of a read in the flow covered by `revFlow`.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate revFlowConsCand(Content c, Configuration config) {
|
|
exists(NodeEx mid, NodeEx node, ContentSet cs |
|
|
fwdFlow(node, pragma[only_bind_into](config)) and
|
|
readSet(node, cs, mid, config) and
|
|
fwdFlowConsCandSet(cs, c, pragma[only_bind_into](config)) and
|
|
revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config))
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) {
|
|
exists(NodeEx mid, TypedContent tc |
|
|
revFlow(mid, toReturn, pragma[only_bind_into](config)) and
|
|
fwdFlowConsCand(c, pragma[only_bind_into](config)) and
|
|
store(node, tc, mid, _, config) and
|
|
c = tc.getContent()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `c` is the target of both a read and a store in the flow covered
|
|
* by `revFlow`.
|
|
*/
|
|
pragma[nomagic]
|
|
additional predicate revFlowIsReadAndStored(Content c, Configuration conf) {
|
|
revFlowConsCand(c, conf) and
|
|
revFlowStore(c, _, _, conf)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
additional predicate viableReturnPosOutNodeCandFwd1(
|
|
DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config
|
|
) {
|
|
fwdFlowReturnPosition(pos, _, config) and
|
|
viableReturnPosOutEx(call, pos, out)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate revFlowOut(ReturnPosition pos, Configuration config) {
|
|
exists(DataFlowCall call, NodeEx out |
|
|
revFlow(out, _, config) and
|
|
viableReturnPosOutNodeCandFwd1(call, pos, out, config)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
additional predicate viableParamArgNodeCandFwd1(
|
|
DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config
|
|
) {
|
|
viableParamArgEx(call, p, arg) and
|
|
fwdFlow(arg, config)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate revFlowIn(
|
|
DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config
|
|
) {
|
|
exists(ParamNodeEx p |
|
|
revFlow(p, toReturn, config) and
|
|
viableParamArgNodeCandFwd1(call, p, arg, config)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) {
|
|
revFlowIn(call, arg, true, config)
|
|
}
|
|
|
|
/**
|
|
* Holds if an output from `call` is reached in the flow covered by `revFlow`
|
|
* and data might flow through the target callable resulting in reverse flow
|
|
* reaching an argument of `call`.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) {
|
|
exists(NodeEx out |
|
|
revFlow(out, toReturn, config) and
|
|
fwdFlowOutFromArg(call, out, config)
|
|
)
|
|
}
|
|
|
|
private predicate stateStepRev(FlowState state1, FlowState state2, Configuration config) {
|
|
exists(NodeEx node1, NodeEx node2 |
|
|
additionalLocalStateStep(node1, state1, node2, state2, config) or
|
|
additionalJumpStateStep(node1, state1, node2, state2, config)
|
|
|
|
|
revFlow(node1, _, pragma[only_bind_into](config)) and
|
|
revFlow(node2, _, pragma[only_bind_into](config)) and
|
|
fwdFlowState(state1, pragma[only_bind_into](config)) and
|
|
fwdFlowState(state2, pragma[only_bind_into](config))
|
|
)
|
|
}
|
|
|
|
additional predicate revFlowState(FlowState state, Configuration config) {
|
|
exists(NodeEx node |
|
|
sinkNode(node, state, config) and
|
|
revFlow(node, _, pragma[only_bind_into](config)) and
|
|
fwdFlowState(state, pragma[only_bind_into](config))
|
|
)
|
|
or
|
|
exists(FlowState state0 |
|
|
revFlowState(state0, config) and
|
|
stateStepRev(state, state0, config)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
predicate storeStepCand(
|
|
NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType,
|
|
Configuration config
|
|
) {
|
|
exists(Content c |
|
|
revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and
|
|
revFlow(node2, pragma[only_bind_into](config)) and
|
|
store(node1, tc, node2, contentType, config) and
|
|
c = tc.getContent() and
|
|
exists(ap1)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) {
|
|
revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and
|
|
read(n1, c, n2, pragma[only_bind_into](config)) and
|
|
revFlow(n2, pragma[only_bind_into](config))
|
|
}
|
|
|
|
pragma[nomagic]
|
|
predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) }
|
|
|
|
bindingset[node, state, config]
|
|
predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) {
|
|
revFlow(node, _, pragma[only_bind_into](config)) and
|
|
exists(state) and
|
|
exists(ap)
|
|
}
|
|
|
|
private predicate throughFlowNodeCand(NodeEx node, Configuration config) {
|
|
revFlow(node, true, config) and
|
|
fwdFlow(node, true, config) and
|
|
not inBarrier(node, config) and
|
|
not outBarrier(node, config)
|
|
}
|
|
|
|
/** Holds if flow may return from `callable`. */
|
|
pragma[nomagic]
|
|
private predicate returnFlowCallableNodeCand(
|
|
DataFlowCallable callable, ReturnKindExt kind, Configuration config
|
|
) {
|
|
exists(RetNodeEx ret |
|
|
throughFlowNodeCand(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.
|
|
*/
|
|
pragma[nomagic]
|
|
predicate parameterMayFlowThrough(ParamNodeEx p, DataFlowCallable c, Ap ap, Configuration config) {
|
|
exists(ReturnKindExt kind |
|
|
throughFlowNodeCand(p, config) and
|
|
returnFlowCallableNodeCand(c, kind, config) and
|
|
p.getEnclosingCallable() = c and
|
|
exists(ap) and
|
|
// we don't expect a parameter to return stored in itself, unless explicitly allowed
|
|
(
|
|
not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
|
|
or
|
|
p.allowParameterReturnInSelf()
|
|
)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
|
|
exists(ArgNodeEx arg, boolean toReturn |
|
|
revFlow(arg, toReturn, config) and
|
|
revFlowInToReturn(call, arg, config) and
|
|
revFlowIsReturned(call, toReturn, config)
|
|
)
|
|
}
|
|
|
|
additional predicate stats(
|
|
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
|
|
) {
|
|
fwd = true and
|
|
nodes = count(NodeEx node | fwdFlow(node, config)) and
|
|
fields = count(Content f0 | fwdFlowConsCand(f0, config)) and
|
|
conscand = -1 and
|
|
states = count(FlowState state | fwdFlowState(state, config)) and
|
|
tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config))
|
|
or
|
|
fwd = false and
|
|
nodes = count(NodeEx node | revFlow(node, _, config)) and
|
|
fields = count(Content f0 | revFlowConsCand(f0, config)) and
|
|
conscand = -1 and
|
|
states = count(FlowState state | revFlowState(state, config)) and
|
|
tuples = count(NodeEx n, boolean b | revFlow(n, b, config))
|
|
}
|
|
/* End: Stage 1 logic. */
|
|
}
|
|
|
|
pragma[noinline]
|
|
private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) {
|
|
Stage1::revFlow(node2, config) and
|
|
localFlowStep(node1, node2, config)
|
|
}
|
|
|
|
pragma[noinline]
|
|
private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) {
|
|
Stage1::revFlow(node2, config) and
|
|
additionalLocalFlowStep(node1, node2, config)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate viableReturnPosOutNodeCand1(
|
|
DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config
|
|
) {
|
|
Stage1::revFlow(out, config) and
|
|
Stage1::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, RetNodeEx ret, NodeEx out, Configuration config
|
|
) {
|
|
viableReturnPosOutNodeCand1(call, ret.getReturnPosition(), out, config) and
|
|
Stage1::revFlow(ret, config) and
|
|
not outBarrier(ret, config) and
|
|
not inBarrier(out, config)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate viableParamArgNodeCand1(
|
|
DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config
|
|
) {
|
|
Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and
|
|
Stage1::revFlow(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, ArgNodeEx arg, ParamNodeEx p, Configuration config
|
|
) {
|
|
viableParamArgNodeCand1(call, p, arg, config) and
|
|
Stage1::revFlow(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(NodeEx n1, Configuration conf) {
|
|
result =
|
|
strictcount(NodeEx 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(NodeEx n2, Configuration conf) {
|
|
result =
|
|
strictcount(NodeEx 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, RetNodeEx ret, NodeEx 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, ArgNodeEx arg, ParamNodeEx 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
|
|
)
|
|
}
|
|
|
|
private signature module StageSig {
|
|
class Ap;
|
|
|
|
predicate revFlow(NodeEx node, Configuration config);
|
|
|
|
bindingset[node, state, config]
|
|
predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config);
|
|
|
|
predicate callMayFlowThroughRev(DataFlowCall call, Configuration config);
|
|
|
|
predicate parameterMayFlowThrough(ParamNodeEx p, DataFlowCallable c, Ap ap, Configuration config);
|
|
|
|
predicate storeStepCand(
|
|
NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType,
|
|
Configuration config
|
|
);
|
|
|
|
predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config);
|
|
}
|
|
|
|
private module MkStage<StageSig PrevStage> {
|
|
class ApApprox = PrevStage::Ap;
|
|
|
|
signature module StageParam {
|
|
class Ap;
|
|
|
|
class ApNil extends Ap;
|
|
|
|
bindingset[result, ap]
|
|
ApApprox getApprox(Ap ap);
|
|
|
|
ApNil getApNil(NodeEx node);
|
|
|
|
bindingset[tc, tail]
|
|
Ap apCons(TypedContent tc, Ap tail);
|
|
|
|
Content getHeadContent(Ap ap);
|
|
|
|
class ApOption;
|
|
|
|
ApOption apNone();
|
|
|
|
ApOption apSome(Ap ap);
|
|
|
|
class Cc;
|
|
|
|
class CcCall extends Cc;
|
|
|
|
// TODO: member predicate on CcCall
|
|
predicate matchesCall(CcCall cc, DataFlowCall call);
|
|
|
|
class CcNoCall extends Cc;
|
|
|
|
Cc ccNone();
|
|
|
|
CcCall ccSomeCall();
|
|
|
|
class LocalCc;
|
|
|
|
bindingset[call, c, outercc]
|
|
CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc);
|
|
|
|
bindingset[call, c, innercc]
|
|
CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc);
|
|
|
|
bindingset[node, cc]
|
|
LocalCc getLocalCc(NodeEx node, Cc cc);
|
|
|
|
bindingset[node1, state1, config]
|
|
bindingset[node2, state2, config]
|
|
predicate localStep(
|
|
NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue,
|
|
ApNil ap, Configuration config, LocalCc lcc
|
|
);
|
|
|
|
predicate flowOutOfCall(
|
|
DataFlowCall call, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, Configuration config
|
|
);
|
|
|
|
predicate flowIntoCall(
|
|
DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config
|
|
);
|
|
|
|
bindingset[node, state, ap, config]
|
|
predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config);
|
|
|
|
bindingset[ap, contentType]
|
|
predicate typecheckStore(Ap ap, DataFlowType contentType);
|
|
}
|
|
|
|
module Stage<StageParam Param> implements StageSig {
|
|
import Param
|
|
|
|
/* Begin: Stage logic. */
|
|
bindingset[result, apa]
|
|
private ApApprox unbindApa(ApApprox apa) {
|
|
pragma[only_bind_out](apa) = pragma[only_bind_out](result)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowThroughOutOfCall(
|
|
DataFlowCall call, CcCall ccc, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow,
|
|
Configuration config
|
|
) {
|
|
flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and
|
|
PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and
|
|
PrevStage::parameterMayFlowThrough(_, ret.getEnclosingCallable(), _,
|
|
pragma[only_bind_into](config)) and
|
|
matchesCall(ccc, call)
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
pragma[nomagic]
|
|
additional predicate fwdFlow(
|
|
NodeEx node, FlowState state, Cc cc, ApOption argAp, Ap ap, Configuration config
|
|
) {
|
|
fwdFlow0(node, state, cc, argAp, ap, config) and
|
|
PrevStage::revFlow(node, state, unbindApa(getApprox(ap)), config) and
|
|
filter(node, state, ap, config)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate fwdFlow0(
|
|
NodeEx node, FlowState state, Cc cc, ApOption argAp, Ap ap, Configuration config
|
|
) {
|
|
sourceNode(node, state, config) and
|
|
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
|
argAp = apNone() and
|
|
ap = getApNil(node)
|
|
or
|
|
exists(NodeEx mid, FlowState state0, Ap ap0, LocalCc localCc |
|
|
fwdFlow(mid, state0, cc, argAp, ap0, config) and
|
|
localCc = getLocalCc(mid, cc)
|
|
|
|
|
localStep(mid, state0, node, state, true, _, config, localCc) and
|
|
ap = ap0
|
|
or
|
|
localStep(mid, state0, node, state, false, ap, config, localCc) and
|
|
ap0 instanceof ApNil
|
|
)
|
|
or
|
|
exists(NodeEx mid |
|
|
fwdFlow(mid, pragma[only_bind_into](state), _, _, ap, pragma[only_bind_into](config)) and
|
|
jumpStep(mid, node, config) and
|
|
cc = ccNone() and
|
|
argAp = apNone()
|
|
)
|
|
or
|
|
exists(NodeEx mid, ApNil nil |
|
|
fwdFlow(mid, state, _, _, nil, pragma[only_bind_into](config)) and
|
|
additionalJumpStep(mid, node, config) and
|
|
cc = ccNone() and
|
|
argAp = apNone() and
|
|
ap = getApNil(node)
|
|
)
|
|
or
|
|
exists(NodeEx mid, FlowState state0, ApNil nil |
|
|
fwdFlow(mid, state0, _, _, nil, pragma[only_bind_into](config)) and
|
|
additionalJumpStateStep(mid, state0, node, state, config) and
|
|
cc = ccNone() and
|
|
argAp = apNone() and
|
|
ap = getApNil(node)
|
|
)
|
|
or
|
|
// store
|
|
exists(TypedContent tc, Ap ap0 |
|
|
fwdFlowStore(_, ap0, tc, node, state, cc, argAp, config) and
|
|
ap = apCons(tc, ap0)
|
|
)
|
|
or
|
|
// read
|
|
exists(Ap ap0, Content c |
|
|
fwdFlowRead(ap0, c, _, node, state, cc, argAp, config) and
|
|
fwdFlowConsCand(ap0, c, ap, config)
|
|
)
|
|
or
|
|
// flow into a callable
|
|
exists(ApApprox apa |
|
|
fwdFlowIn(_, node, state, _, cc, _, ap, config) and
|
|
apa = getApprox(ap) and
|
|
if PrevStage::parameterMayFlowThrough(node, _, apa, config)
|
|
then argAp = apSome(ap)
|
|
else argAp = apNone()
|
|
)
|
|
or
|
|
// flow out of a callable
|
|
fwdFlowOutNotFromArg(node, state, cc, argAp, ap, config)
|
|
or
|
|
exists(DataFlowCall call, Ap argAp0 |
|
|
fwdFlowOutFromArg(call, node, state, argAp0, ap, config) and
|
|
fwdFlowIsEntered(call, cc, argAp, argAp0, config)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate fwdFlowStore(
|
|
NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, FlowState state, Cc cc, ApOption argAp,
|
|
Configuration config
|
|
) {
|
|
exists(DataFlowType contentType |
|
|
fwdFlow(node1, state, cc, argAp, ap1, config) and
|
|
PrevStage::storeStepCand(node1, unbindApa(getApprox(ap1)), tc, node2, contentType, config) and
|
|
typecheckStore(ap1, contentType)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if forward flow with access path `tail` reaches a store of `c`
|
|
* resulting in access path `cons`.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) {
|
|
exists(TypedContent tc |
|
|
fwdFlowStore(_, tail, tc, _, _, _, _, config) and
|
|
tc.getContent() = c and
|
|
cons = apCons(tc, tail)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate fwdFlowRead(
|
|
Ap ap, Content c, NodeEx node1, NodeEx node2, FlowState state, Cc cc, ApOption argAp,
|
|
Configuration config
|
|
) {
|
|
fwdFlow(node1, state, cc, argAp, ap, config) and
|
|
PrevStage::readStepCand(node1, c, node2, config) and
|
|
getHeadContent(ap) = c
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate fwdFlowIn(
|
|
DataFlowCall call, ParamNodeEx p, FlowState state, Cc outercc, Cc innercc, ApOption argAp,
|
|
Ap ap, Configuration config
|
|
) {
|
|
exists(ArgNodeEx arg, boolean allowsFieldFlow |
|
|
fwdFlow(arg, state, outercc, argAp, ap, config) and
|
|
flowIntoCall(call, arg, p, allowsFieldFlow, config) and
|
|
innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) and
|
|
if allowsFieldFlow = false then ap instanceof ApNil else any()
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate fwdFlowOutNotFromArg(
|
|
NodeEx out, FlowState state, Cc ccOut, ApOption argAp, Ap ap, Configuration config
|
|
) {
|
|
exists(
|
|
DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc,
|
|
DataFlowCallable inner
|
|
|
|
|
fwdFlow(ret, state, innercc, argAp, ap, config) and
|
|
flowOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
|
inner = ret.getEnclosingCallable() and
|
|
ccOut = getCallContextReturn(inner, call, innercc) and
|
|
if allowsFieldFlow = false then ap instanceof ApNil else any()
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate fwdFlowOutFromArg(
|
|
DataFlowCall call, NodeEx out, FlowState state, Ap argAp, Ap ap, Configuration config
|
|
) {
|
|
exists(RetNodeEx ret, boolean allowsFieldFlow, CcCall ccc |
|
|
fwdFlow(ret, state, ccc, apSome(argAp), ap, config) and
|
|
flowThroughOutOfCall(call, ccc, ret, out, allowsFieldFlow, config) and
|
|
if allowsFieldFlow = false then ap instanceof ApNil else any()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if an argument to `call` is reached in the flow covered by `fwdFlow`
|
|
* and data might flow through the target callable and back out at `call`.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate fwdFlowIsEntered(
|
|
DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config
|
|
) {
|
|
exists(ParamNodeEx p |
|
|
fwdFlowIn(call, p, _, cc, _, argAp, ap, config) and
|
|
PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate storeStepFwd(
|
|
NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config
|
|
) {
|
|
fwdFlowStore(node1, ap1, tc, node2, _, _, _, config) and
|
|
ap2 = apCons(tc, ap1) and
|
|
fwdFlowRead(ap2, tc.getContent(), _, _, _, _, _, config)
|
|
}
|
|
|
|
private predicate readStepFwd(
|
|
NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config
|
|
) {
|
|
fwdFlowRead(ap1, c, n1, n2, _, _, _, config) and
|
|
fwdFlowConsCand(ap1, c, ap2, config)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
|
|
exists(Ap argAp0, NodeEx out, FlowState state, Cc cc, ApOption argAp, Ap ap |
|
|
fwdFlow(out, state, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
|
|
pragma[only_bind_into](config)) and
|
|
fwdFlowOutFromArg(call, out, state, argAp0, ap, config) and
|
|
fwdFlowIsEntered(pragma[only_bind_into](call), pragma[only_bind_into](cc),
|
|
pragma[only_bind_into](argAp), pragma[only_bind_into](argAp0),
|
|
pragma[only_bind_into](config))
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowThroughIntoCall(
|
|
DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config
|
|
) {
|
|
flowIntoCall(call, arg, p, allowsFieldFlow, config) and
|
|
fwdFlow(arg, _, _, _, _, pragma[only_bind_into](config)) and
|
|
PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and
|
|
callMayFlowThroughFwd(call, pragma[only_bind_into](config))
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate returnNodeMayFlowThrough(
|
|
RetNodeEx ret, FlowState state, Ap ap, Configuration config
|
|
) {
|
|
fwdFlow(ret, state, any(CcCall ccc), apSome(_), ap, 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.
|
|
*/
|
|
pragma[nomagic]
|
|
additional predicate revFlow(
|
|
NodeEx node, FlowState state, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
|
|
) {
|
|
revFlow0(node, state, toReturn, returnAp, ap, config) and
|
|
fwdFlow(node, state, _, _, ap, config)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate revFlow0(
|
|
NodeEx node, FlowState state, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
|
|
) {
|
|
fwdFlow(node, state, _, _, ap, config) and
|
|
sinkNode(node, state, config) and
|
|
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
|
returnAp = apNone() and
|
|
ap instanceof ApNil
|
|
or
|
|
exists(NodeEx mid, FlowState state0 |
|
|
localStep(node, state, mid, state0, true, _, config, _) and
|
|
revFlow(mid, state0, toReturn, returnAp, ap, config)
|
|
)
|
|
or
|
|
exists(NodeEx mid, FlowState state0, ApNil nil |
|
|
fwdFlow(node, pragma[only_bind_into](state), _, _, ap, pragma[only_bind_into](config)) and
|
|
localStep(node, pragma[only_bind_into](state), mid, state0, false, _, config, _) and
|
|
revFlow(mid, state0, toReturn, returnAp, nil, pragma[only_bind_into](config)) and
|
|
ap instanceof ApNil
|
|
)
|
|
or
|
|
exists(NodeEx mid |
|
|
jumpStep(node, mid, config) and
|
|
revFlow(mid, state, _, _, ap, config) and
|
|
toReturn = false and
|
|
returnAp = apNone()
|
|
)
|
|
or
|
|
exists(NodeEx mid, ApNil nil |
|
|
fwdFlow(node, _, _, _, ap, pragma[only_bind_into](config)) and
|
|
additionalJumpStep(node, mid, config) and
|
|
revFlow(pragma[only_bind_into](mid), state, _, _, nil, pragma[only_bind_into](config)) and
|
|
toReturn = false and
|
|
returnAp = apNone() and
|
|
ap instanceof ApNil
|
|
)
|
|
or
|
|
exists(NodeEx mid, FlowState state0, ApNil nil |
|
|
fwdFlow(node, _, _, _, ap, pragma[only_bind_into](config)) and
|
|
additionalJumpStateStep(node, state, mid, state0, config) and
|
|
revFlow(pragma[only_bind_into](mid), pragma[only_bind_into](state0), _, _, nil,
|
|
pragma[only_bind_into](config)) and
|
|
toReturn = false and
|
|
returnAp = apNone() and
|
|
ap instanceof ApNil
|
|
)
|
|
or
|
|
// store
|
|
exists(Ap ap0, Content c |
|
|
revFlowStore(ap0, c, ap, node, state, _, _, toReturn, returnAp, config) and
|
|
revFlowConsCand(ap0, c, ap, config)
|
|
)
|
|
or
|
|
// read
|
|
exists(NodeEx mid, Ap ap0 |
|
|
revFlow(mid, state, toReturn, returnAp, ap0, config) and
|
|
readStepFwd(node, ap, _, mid, ap0, config)
|
|
)
|
|
or
|
|
// flow into a callable
|
|
revFlowInNotToReturn(node, state, returnAp, ap, config) and
|
|
toReturn = false
|
|
or
|
|
exists(DataFlowCall call, Ap returnAp0 |
|
|
revFlowInToReturn(call, node, state, returnAp0, ap, config) and
|
|
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
|
)
|
|
or
|
|
// flow out of a callable
|
|
revFlowOut(_, node, state, _, _, ap, config) and
|
|
toReturn = true and
|
|
if returnNodeMayFlowThrough(node, state, ap, config)
|
|
then returnAp = apSome(ap)
|
|
else returnAp = apNone()
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate revFlowStore(
|
|
Ap ap0, Content c, Ap ap, NodeEx node, FlowState state, TypedContent tc, NodeEx mid,
|
|
boolean toReturn, ApOption returnAp, Configuration config
|
|
) {
|
|
revFlow(mid, state, toReturn, returnAp, ap0, config) and
|
|
storeStepFwd(node, ap, tc, mid, ap0, config) and
|
|
tc.getContent() = c
|
|
}
|
|
|
|
/**
|
|
* Holds if reverse flow with access path `tail` reaches a read of `c`
|
|
* resulting in access path `cons`.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) {
|
|
exists(NodeEx mid, Ap tail0 |
|
|
revFlow(mid, _, _, _, tail, config) and
|
|
tail = pragma[only_bind_into](tail0) and
|
|
readStepFwd(_, cons, c, mid, tail0, config)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate revFlowOut(
|
|
DataFlowCall call, RetNodeEx ret, FlowState state, boolean toReturn, ApOption returnAp, Ap ap,
|
|
Configuration config
|
|
) {
|
|
exists(NodeEx out, boolean allowsFieldFlow |
|
|
revFlow(out, state, toReturn, returnAp, ap, config) and
|
|
flowOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
|
if allowsFieldFlow = false then ap instanceof ApNil else any()
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate revFlowInNotToReturn(
|
|
ArgNodeEx arg, FlowState state, ApOption returnAp, Ap ap, Configuration config
|
|
) {
|
|
exists(ParamNodeEx p, boolean allowsFieldFlow |
|
|
revFlow(p, state, false, returnAp, ap, config) and
|
|
flowIntoCall(_, arg, p, allowsFieldFlow, config) and
|
|
if allowsFieldFlow = false then ap instanceof ApNil else any()
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate revFlowInToReturn(
|
|
DataFlowCall call, ArgNodeEx arg, FlowState state, Ap returnAp, Ap ap, Configuration config
|
|
) {
|
|
exists(ParamNodeEx p, boolean allowsFieldFlow |
|
|
revFlow(p, state, true, apSome(returnAp), ap, config) and
|
|
flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) and
|
|
if allowsFieldFlow = false then ap instanceof ApNil else any()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if an output from `call` is reached in the flow covered by `revFlow`
|
|
* and data might flow through the target callable resulting in reverse flow
|
|
* reaching an argument of `call`.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate revFlowIsReturned(
|
|
DataFlowCall call, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
|
|
) {
|
|
exists(RetNodeEx ret, FlowState state, CcCall ccc |
|
|
revFlowOut(call, ret, state, toReturn, returnAp, ap, config) and
|
|
fwdFlow(ret, state, ccc, apSome(_), ap, config) and
|
|
matchesCall(ccc, call)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
predicate storeStepCand(
|
|
NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType,
|
|
Configuration config
|
|
) {
|
|
exists(Ap ap2, Content c |
|
|
PrevStage::storeStepCand(node1, _, tc, node2, contentType, config) and
|
|
revFlowStore(ap2, c, ap1, node1, _, tc, node2, _, _, config) and
|
|
revFlowConsCand(ap2, c, ap1, config)
|
|
)
|
|
}
|
|
|
|
predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) {
|
|
exists(Ap ap1, Ap ap2 |
|
|
revFlow(node2, _, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and
|
|
readStepFwd(node1, ap1, c, node2, ap2, config) and
|
|
revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, _,
|
|
pragma[only_bind_into](config))
|
|
)
|
|
}
|
|
|
|
additional predicate revFlow(NodeEx node, FlowState state, Configuration config) {
|
|
revFlow(node, state, _, _, _, config)
|
|
}
|
|
|
|
predicate revFlow(NodeEx node, FlowState state, Ap ap, Configuration config) {
|
|
revFlow(node, state, _, _, ap, config)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) }
|
|
|
|
// use an alias as a workaround for bad functionality-induced joins
|
|
pragma[nomagic]
|
|
additional predicate revFlowAlias(NodeEx node, Configuration config) {
|
|
revFlow(node, _, _, _, _, config)
|
|
}
|
|
|
|
// use an alias as a workaround for bad functionality-induced joins
|
|
pragma[nomagic]
|
|
additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
|
|
revFlow(node, state, ap, config)
|
|
}
|
|
|
|
private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) {
|
|
storeStepFwd(_, ap, tc, _, _, config)
|
|
}
|
|
|
|
private predicate revConsCand(TypedContent tc, Ap ap, Configuration config) {
|
|
storeStepCand(_, ap, tc, _, _, config)
|
|
}
|
|
|
|
private predicate validAp(Ap ap, Configuration config) {
|
|
revFlow(_, _, _, _, ap, config) and ap instanceof ApNil
|
|
or
|
|
exists(TypedContent head, Ap tail |
|
|
consCand(head, tail, config) and
|
|
ap = apCons(head, tail)
|
|
)
|
|
}
|
|
|
|
additional predicate consCand(TypedContent tc, Ap ap, Configuration config) {
|
|
revConsCand(tc, ap, config) and
|
|
validAp(ap, config)
|
|
}
|
|
|
|
pragma[noinline]
|
|
private predicate parameterFlow(
|
|
ParamNodeEx p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config
|
|
) {
|
|
revFlow(p, _, true, apSome(ap0), ap, config) and
|
|
c = p.getEnclosingCallable()
|
|
}
|
|
|
|
predicate parameterMayFlowThrough(ParamNodeEx p, DataFlowCallable c, Ap ap, Configuration config) {
|
|
exists(RetNodeEx ret, FlowState state, Ap ap0, ReturnKindExt kind, ParameterPosition pos |
|
|
parameterFlow(p, ap, ap0, c, config) and
|
|
c = ret.getEnclosingCallable() and
|
|
revFlow(pragma[only_bind_into](ret), pragma[only_bind_into](state), true, apSome(_),
|
|
pragma[only_bind_into](ap0), pragma[only_bind_into](config)) and
|
|
fwdFlow(ret, state, any(CcCall ccc), apSome(ap), ap0, config) and
|
|
kind = ret.getKind() and
|
|
p.getPosition() = pos and
|
|
// we don't expect a parameter to return stored in itself, unless explicitly allowed
|
|
(
|
|
not kind.(ParamUpdateReturnKind).getPosition() = pos
|
|
or
|
|
p.allowParameterReturnInSelf()
|
|
)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
|
|
exists(
|
|
Ap returnAp0, ArgNodeEx arg, FlowState state, boolean toReturn, ApOption returnAp, Ap ap
|
|
|
|
|
revFlow(arg, state, toReturn, returnAp, ap, config) and
|
|
revFlowInToReturn(call, arg, state, returnAp0, ap, config) and
|
|
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
|
)
|
|
}
|
|
|
|
additional predicate stats(
|
|
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
|
|
) {
|
|
fwd = true and
|
|
nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, config)) and
|
|
fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and
|
|
conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and
|
|
states = count(FlowState state | fwdFlow(_, state, _, _, _, config)) and
|
|
tuples =
|
|
count(NodeEx n, FlowState state, Cc cc, ApOption argAp, Ap ap |
|
|
fwdFlow(n, state, cc, argAp, ap, config)
|
|
)
|
|
or
|
|
fwd = false and
|
|
nodes = count(NodeEx node | revFlow(node, _, _, _, _, config)) and
|
|
fields = count(TypedContent f0 | consCand(f0, _, config)) and
|
|
conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and
|
|
states = count(FlowState state | revFlow(_, state, _, _, _, config)) and
|
|
tuples =
|
|
count(NodeEx n, FlowState state, boolean b, ApOption retAp, Ap ap |
|
|
revFlow(n, state, b, retAp, ap, config)
|
|
)
|
|
}
|
|
/* End: Stage logic. */
|
|
}
|
|
}
|
|
|
|
private module BooleanCallContext {
|
|
class Cc extends boolean {
|
|
Cc() { this in [true, false] }
|
|
}
|
|
|
|
class CcCall extends Cc {
|
|
CcCall() { this = true }
|
|
}
|
|
|
|
/** Holds if the call context may be `call`. */
|
|
predicate matchesCall(CcCall cc, DataFlowCall call) { any() }
|
|
|
|
class CcNoCall extends Cc {
|
|
CcNoCall() { this = false }
|
|
}
|
|
|
|
Cc ccNone() { result = false }
|
|
|
|
CcCall ccSomeCall() { result = true }
|
|
|
|
class LocalCc = Unit;
|
|
|
|
bindingset[node, cc]
|
|
LocalCc getLocalCc(NodeEx node, Cc cc) { any() }
|
|
|
|
bindingset[call, c, outercc]
|
|
CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
|
|
|
|
bindingset[call, c, innercc]
|
|
CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
|
|
}
|
|
|
|
private module Level1CallContext {
|
|
class Cc = CallContext;
|
|
|
|
class CcCall = CallContextCall;
|
|
|
|
pragma[inline]
|
|
predicate matchesCall(CcCall cc, DataFlowCall call) { cc.matchesCall(call) }
|
|
|
|
class CcNoCall = CallContextNoCall;
|
|
|
|
Cc ccNone() { result instanceof CallContextAny }
|
|
|
|
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
|
|
|
module NoLocalCallContext {
|
|
class LocalCc = Unit;
|
|
|
|
bindingset[node, cc]
|
|
LocalCc getLocalCc(NodeEx node, Cc cc) { any() }
|
|
|
|
bindingset[call, c, outercc]
|
|
CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
|
|
checkCallContextCall(outercc, call, c) and
|
|
if recordDataFlowCallSiteDispatch(call, c)
|
|
then result = TSpecificCall(call)
|
|
else result = TSomeCall()
|
|
}
|
|
}
|
|
|
|
module LocalCallContext {
|
|
class LocalCc = LocalCallContext;
|
|
|
|
bindingset[node, cc]
|
|
LocalCc getLocalCc(NodeEx node, Cc cc) {
|
|
result =
|
|
getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)),
|
|
node.getEnclosingCallable())
|
|
}
|
|
|
|
bindingset[call, c, outercc]
|
|
CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
|
|
checkCallContextCall(outercc, call, c) and
|
|
if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall()
|
|
}
|
|
}
|
|
|
|
bindingset[call, c, innercc]
|
|
CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
|
|
checkCallContextReturn(innercc, c, call) and
|
|
if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
|
|
}
|
|
}
|
|
|
|
private module Stage2Param implements MkStage<Stage1>::StageParam {
|
|
private module PrevStage = Stage1;
|
|
|
|
class Ap extends boolean {
|
|
Ap() { this in [true, false] }
|
|
}
|
|
|
|
class ApNil extends Ap {
|
|
ApNil() { this = false }
|
|
}
|
|
|
|
bindingset[result, ap]
|
|
PrevStage::Ap getApprox(Ap ap) { any() }
|
|
|
|
ApNil getApNil(NodeEx node) { Stage1::revFlow(node, _) and exists(result) }
|
|
|
|
bindingset[tc, tail]
|
|
Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) }
|
|
|
|
pragma[inline]
|
|
Content getHeadContent(Ap ap) { exists(result) and ap = true }
|
|
|
|
class ApOption = BooleanOption;
|
|
|
|
ApOption apNone() { result = TBooleanNone() }
|
|
|
|
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
|
|
|
|
import Level1CallContext
|
|
import NoLocalCallContext
|
|
|
|
bindingset[node1, state1, config]
|
|
bindingset[node2, state2, config]
|
|
predicate localStep(
|
|
NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue,
|
|
ApNil ap, Configuration config, LocalCc lcc
|
|
) {
|
|
(
|
|
preservesValue = true and
|
|
localFlowStepNodeCand1(node1, node2, config) and
|
|
state1 = state2
|
|
or
|
|
preservesValue = false and
|
|
additionalLocalFlowStepNodeCand1(node1, node2, config) and
|
|
state1 = state2
|
|
or
|
|
preservesValue = false and
|
|
additionalLocalStateStep(node1, state1, node2, state2, config)
|
|
) and
|
|
exists(ap) and
|
|
exists(lcc)
|
|
}
|
|
|
|
predicate flowOutOfCall = flowOutOfCallNodeCand1/5;
|
|
|
|
predicate flowIntoCall = flowIntoCallNodeCand1/5;
|
|
|
|
pragma[nomagic]
|
|
private predicate expectsContentCand(NodeEx node, Configuration config) {
|
|
exists(Content c |
|
|
PrevStage::revFlow(node, pragma[only_bind_into](config)) and
|
|
PrevStage::revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and
|
|
expectsContentEx(node, c)
|
|
)
|
|
}
|
|
|
|
bindingset[node, state, ap, config]
|
|
predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) {
|
|
PrevStage::revFlowState(state, pragma[only_bind_into](config)) and
|
|
exists(ap) and
|
|
not stateBarrier(node, state, config) and
|
|
(
|
|
notExpectsContent(node)
|
|
or
|
|
ap = true and
|
|
expectsContentCand(node, config)
|
|
)
|
|
}
|
|
|
|
bindingset[ap, contentType]
|
|
predicate typecheckStore(Ap ap, DataFlowType contentType) { any() }
|
|
}
|
|
|
|
private module Stage2 implements StageSig {
|
|
import MkStage<Stage1>::Stage<Stage2Param>
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowOutOfCallNodeCand2(
|
|
DataFlowCall call, RetNodeEx node1, NodeEx node2, boolean allowsFieldFlow, Configuration config
|
|
) {
|
|
flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and
|
|
Stage2::revFlow(node2, pragma[only_bind_into](config)) and
|
|
Stage2::revFlowAlias(node1, pragma[only_bind_into](config))
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate flowIntoCallNodeCand2(
|
|
DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow,
|
|
Configuration config
|
|
) {
|
|
flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and
|
|
Stage2::revFlow(node2, pragma[only_bind_into](config)) and
|
|
Stage2::revFlowAlias(node1, pragma[only_bind_into](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 NodeEx {
|
|
FlowCheckNode() {
|
|
castNode(this.asNode()) or
|
|
clearsContentCached(this.asNode(), _) or
|
|
expectsContentCached(this.asNode(), _)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Holds if `node` can be the first node in a maximal subsequence of local
|
|
* flow steps in a dataflow path.
|
|
*/
|
|
private predicate localFlowEntry(NodeEx node, FlowState state, Configuration config) {
|
|
Stage2::revFlow(node, state, config) and
|
|
(
|
|
sourceNode(node, state, config)
|
|
or
|
|
jumpStep(_, node, config)
|
|
or
|
|
additionalJumpStep(_, node, config)
|
|
or
|
|
additionalJumpStateStep(_, _, node, state, config)
|
|
or
|
|
node instanceof ParamNodeEx
|
|
or
|
|
node.asNode() instanceof OutNodeExt
|
|
or
|
|
Stage2::storeStepCand(_, _, _, node, _, config)
|
|
or
|
|
Stage2::readStepCand(_, _, node, config)
|
|
or
|
|
node instanceof FlowCheckNode
|
|
or
|
|
exists(FlowState s |
|
|
additionalLocalStateStep(_, s, node, state, config) and
|
|
s != state
|
|
)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `node` can be the last node in a maximal subsequence of local
|
|
* flow steps in a dataflow path.
|
|
*/
|
|
private predicate localFlowExit(NodeEx node, FlowState state, Configuration config) {
|
|
exists(NodeEx next | Stage2::revFlow(next, state, config) |
|
|
jumpStep(node, next, config) or
|
|
additionalJumpStep(node, next, config) or
|
|
flowIntoCallNodeCand1(_, node, next, config) or
|
|
flowOutOfCallNodeCand1(_, node, next, config) or
|
|
Stage2::storeStepCand(node, _, _, next, _, config) or
|
|
Stage2::readStepCand(node, _, next, config)
|
|
)
|
|
or
|
|
exists(NodeEx next, FlowState s | Stage2::revFlow(next, s, config) |
|
|
additionalJumpStateStep(node, state, next, s, config)
|
|
or
|
|
additionalLocalStateStep(node, state, next, s, config) and
|
|
s != state
|
|
)
|
|
or
|
|
Stage2::revFlow(node, state, config) and
|
|
node instanceof FlowCheckNode
|
|
or
|
|
sinkNode(node, state, config)
|
|
}
|
|
|
|
pragma[noinline]
|
|
private predicate additionalLocalFlowStepNodeCand2(
|
|
NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, Configuration config
|
|
) {
|
|
additionalLocalFlowStepNodeCand1(node1, node2, config) and
|
|
state1 = state2 and
|
|
Stage2::revFlow(node1, pragma[only_bind_into](state1), false, pragma[only_bind_into](config)) and
|
|
Stage2::revFlowAlias(node2, pragma[only_bind_into](state2), false,
|
|
pragma[only_bind_into](config))
|
|
or
|
|
additionalLocalStateStep(node1, state1, node2, state2, config) and
|
|
Stage2::revFlow(node1, state1, false, pragma[only_bind_into](config)) and
|
|
Stage2::revFlowAlias(node2, state2, false, pragma[only_bind_into](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(
|
|
NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t,
|
|
Configuration config, LocalCallContext cc
|
|
) {
|
|
not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and
|
|
(
|
|
localFlowEntry(node1, pragma[only_bind_into](state), pragma[only_bind_into](config)) and
|
|
(
|
|
localFlowStepNodeCand1(node1, node2, config) and
|
|
preservesValue = true and
|
|
t = node1.getDataFlowType() and // irrelevant dummy value
|
|
Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config))
|
|
or
|
|
additionalLocalFlowStepNodeCand2(node1, state, node2, state, config) and
|
|
preservesValue = false and
|
|
t = node2.getDataFlowType()
|
|
) and
|
|
node1 != node2 and
|
|
cc.relevantFor(node1.getEnclosingCallable()) and
|
|
not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall())
|
|
or
|
|
exists(NodeEx mid |
|
|
localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t,
|
|
pragma[only_bind_into](config), cc) and
|
|
localFlowStepNodeCand1(mid, node2, config) and
|
|
not mid instanceof FlowCheckNode and
|
|
Stage2::revFlow(node2, pragma[only_bind_into](state), pragma[only_bind_into](config))
|
|
)
|
|
or
|
|
exists(NodeEx mid |
|
|
localFlowStepPlus(node1, state, mid, _, _, pragma[only_bind_into](config), cc) and
|
|
additionalLocalFlowStepNodeCand2(mid, state, node2, state, config) and
|
|
not mid instanceof FlowCheckNode and
|
|
preservesValue = false and
|
|
t = node2.getDataFlowType()
|
|
)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* 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(
|
|
NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue,
|
|
AccessPathFrontNil apf, Configuration config, LocalCallContext callContext
|
|
) {
|
|
localFlowStepPlus(node1, state1, node2, preservesValue, apf.getType(), config, callContext) and
|
|
localFlowExit(node2, state1, config) and
|
|
state1 = state2
|
|
or
|
|
additionalLocalFlowStepNodeCand2(node1, state1, node2, state2, config) and
|
|
state1 != state2 and
|
|
preservesValue = false and
|
|
apf = TFrontNil(node2.getDataFlowType()) and
|
|
callContext.relevantFor(node1.getEnclosingCallable()) and
|
|
not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() |
|
|
isUnreachableInCallCached(node1.asNode(), call) or
|
|
isUnreachableInCallCached(node2.asNode(), call)
|
|
)
|
|
}
|
|
}
|
|
|
|
private import LocalFlowBigStep
|
|
|
|
private module Stage3Param implements MkStage<Stage2>::StageParam {
|
|
private module PrevStage = Stage2;
|
|
|
|
class Ap = AccessPathFront;
|
|
|
|
class ApNil = AccessPathFrontNil;
|
|
|
|
PrevStage::Ap getApprox(Ap ap) { result = ap.toBoolNonEmpty() }
|
|
|
|
ApNil getApNil(NodeEx node) {
|
|
PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType())
|
|
}
|
|
|
|
bindingset[tc, tail]
|
|
Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) }
|
|
|
|
pragma[noinline]
|
|
Content getHeadContent(Ap ap) { result = ap.getHead().getContent() }
|
|
|
|
class ApOption = AccessPathFrontOption;
|
|
|
|
ApOption apNone() { result = TAccessPathFrontNone() }
|
|
|
|
ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) }
|
|
|
|
import BooleanCallContext
|
|
|
|
predicate localStep(
|
|
NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue,
|
|
ApNil ap, Configuration config, LocalCc lcc
|
|
) {
|
|
localFlowBigStep(node1, state1, node2, state2, preservesValue, ap, config, _) and exists(lcc)
|
|
}
|
|
|
|
predicate flowOutOfCall = flowOutOfCallNodeCand2/5;
|
|
|
|
predicate flowIntoCall = flowIntoCallNodeCand2/5;
|
|
|
|
pragma[nomagic]
|
|
private predicate clearSet(NodeEx node, ContentSet c, Configuration config) {
|
|
PrevStage::revFlow(node, config) and
|
|
clearsContentCached(node.asNode(), c)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate clearContent(NodeEx node, Content c, Configuration config) {
|
|
exists(ContentSet cs |
|
|
PrevStage::readStepCand(_, pragma[only_bind_into](c), _, pragma[only_bind_into](config)) and
|
|
c = cs.getAReadContent() and
|
|
clearSet(node, cs, pragma[only_bind_into](config))
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate clear(NodeEx node, Ap ap, Configuration config) {
|
|
clearContent(node, ap.getHead().getContent(), config)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate expectsContentCand(NodeEx node, Ap ap, Configuration config) {
|
|
exists(Content c |
|
|
PrevStage::revFlow(node, pragma[only_bind_into](config)) and
|
|
PrevStage::readStepCand(_, c, _, pragma[only_bind_into](config)) and
|
|
expectsContentEx(node, c) and
|
|
c = ap.getHead().getContent()
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode }
|
|
|
|
bindingset[node, state, ap, config]
|
|
predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) {
|
|
exists(state) and
|
|
exists(config) and
|
|
not clear(node, ap, config) and
|
|
(if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()) and
|
|
(
|
|
notExpectsContent(node)
|
|
or
|
|
expectsContentCand(node, ap, config)
|
|
)
|
|
}
|
|
|
|
bindingset[ap, contentType]
|
|
predicate typecheckStore(Ap ap, DataFlowType contentType) {
|
|
// We need to typecheck stores here, since reverse flow through a getter
|
|
// might have a different type here compared to inside the getter.
|
|
compatibleTypes(ap.getType(), contentType)
|
|
}
|
|
}
|
|
|
|
private module Stage3 implements StageSig {
|
|
import MkStage<Stage2>::Stage<Stage3Param>
|
|
}
|
|
|
|
/**
|
|
* Holds if `argApf` is recorded as the summary context for flow reaching `node`
|
|
* and remains relevant for the following pruning stage.
|
|
*/
|
|
private predicate flowCandSummaryCtx(
|
|
NodeEx node, FlowState state, AccessPathFront argApf, Configuration config
|
|
) {
|
|
exists(AccessPathFront apf |
|
|
Stage3::revFlow(node, state, true, _, apf, config) and
|
|
Stage3::fwdFlow(node, state, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if a length 2 access path approximation with the head `tc` is expected
|
|
* to be expensive.
|
|
*/
|
|
private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) {
|
|
exists(int tails, int nodes, int apLimit, int tupleLimit |
|
|
tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and
|
|
nodes =
|
|
strictcount(NodeEx n, FlowState state |
|
|
Stage3::revFlow(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config)
|
|
or
|
|
flowCandSummaryCtx(n, state, any(AccessPathFrontHead apf | apf.getHead() = tc), config)
|
|
) and
|
|
accessPathApproxCostLimits(apLimit, tupleLimit) and
|
|
apLimit < tails and
|
|
tupleLimit < (tails - 1) * nodes and
|
|
not tc.forceHighPrecision()
|
|
)
|
|
}
|
|
|
|
private newtype TAccessPathApprox =
|
|
TNil(DataFlowType t) or
|
|
TConsNil(TypedContent tc, DataFlowType t) {
|
|
Stage3::consCand(tc, TFrontNil(t), _) and
|
|
not expensiveLen2unfolding(tc, _)
|
|
} or
|
|
TConsCons(TypedContent tc1, TypedContent tc2, int len) {
|
|
Stage3::consCand(tc1, TFrontHead(tc2), _) and
|
|
len in [2 .. accessPathLimit()] and
|
|
not expensiveLen2unfolding(tc1, _)
|
|
} or
|
|
TCons1(TypedContent tc, int len) {
|
|
len in [1 .. accessPathLimit()] and
|
|
expensiveLen2unfolding(tc, _)
|
|
}
|
|
|
|
/**
|
|
* Conceptually a list of `TypedContent`s followed by a `DataFlowType`, 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 `AccessPathApprox`, 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 AccessPathApprox extends TAccessPathApprox {
|
|
abstract string toString();
|
|
|
|
abstract TypedContent getHead();
|
|
|
|
abstract int len();
|
|
|
|
abstract DataFlowType getType();
|
|
|
|
abstract AccessPathFront getFront();
|
|
|
|
/** Gets the access path obtained by popping `head` from this path, if any. */
|
|
abstract AccessPathApprox pop(TypedContent head);
|
|
}
|
|
|
|
private class AccessPathApproxNil extends AccessPathApprox, TNil {
|
|
private DataFlowType t;
|
|
|
|
AccessPathApproxNil() { 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 AccessPathApprox pop(TypedContent head) { none() }
|
|
}
|
|
|
|
abstract private class AccessPathApproxCons extends AccessPathApprox { }
|
|
|
|
private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil {
|
|
private TypedContent tc;
|
|
private DataFlowType t;
|
|
|
|
AccessPathApproxConsNil() { 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 AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) }
|
|
}
|
|
|
|
private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons {
|
|
private TypedContent tc1;
|
|
private TypedContent tc2;
|
|
private int len;
|
|
|
|
AccessPathApproxConsCons() { 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 AccessPathApprox pop(TypedContent head) {
|
|
head = tc1 and
|
|
(
|
|
result = TConsCons(tc2, _, len - 1)
|
|
or
|
|
len = 2 and
|
|
result = TConsNil(tc2, _)
|
|
or
|
|
result = TCons1(tc2, len - 1)
|
|
)
|
|
}
|
|
}
|
|
|
|
private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 {
|
|
private TypedContent tc;
|
|
private int len;
|
|
|
|
AccessPathApproxCons1() { this = TCons1(tc, len) }
|
|
|
|
override string toString() {
|
|
if len = 1
|
|
then result = "[" + tc.toString() + "]"
|
|
else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]"
|
|
}
|
|
|
|
override TypedContent getHead() { result = tc }
|
|
|
|
override int len() { result = len }
|
|
|
|
override DataFlowType getType() { result = tc.getContainerType() }
|
|
|
|
override AccessPathFront getFront() { result = TFrontHead(tc) }
|
|
|
|
override AccessPathApprox pop(TypedContent head) {
|
|
head = tc and
|
|
(
|
|
exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) |
|
|
result = TConsCons(tc2, _, len - 1)
|
|
or
|
|
len = 2 and
|
|
result = TConsNil(tc2, _)
|
|
or
|
|
result = TCons1(tc2, len - 1)
|
|
)
|
|
or
|
|
exists(DataFlowType t |
|
|
len = 1 and
|
|
Stage3::consCand(tc, TFrontNil(t), _) and
|
|
result = TNil(t)
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
/** Gets the access path obtained by popping `tc` from `ap`, if any. */
|
|
private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) }
|
|
|
|
/** Gets the access path obtained by pushing `tc` onto `ap`. */
|
|
private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) }
|
|
|
|
private newtype TAccessPathApproxOption =
|
|
TAccessPathApproxNone() or
|
|
TAccessPathApproxSome(AccessPathApprox apa)
|
|
|
|
private class AccessPathApproxOption extends TAccessPathApproxOption {
|
|
string toString() {
|
|
this = TAccessPathApproxNone() and result = "<none>"
|
|
or
|
|
this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString()))
|
|
}
|
|
}
|
|
|
|
private module Stage4Param implements MkStage<Stage3>::StageParam {
|
|
private module PrevStage = Stage3;
|
|
|
|
class Ap = AccessPathApprox;
|
|
|
|
class ApNil = AccessPathApproxNil;
|
|
|
|
PrevStage::Ap getApprox(Ap ap) { result = ap.getFront() }
|
|
|
|
ApNil getApNil(NodeEx node) {
|
|
PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType())
|
|
}
|
|
|
|
bindingset[tc, tail]
|
|
Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) }
|
|
|
|
pragma[noinline]
|
|
Content getHeadContent(Ap ap) { result = ap.getHead().getContent() }
|
|
|
|
class ApOption = AccessPathApproxOption;
|
|
|
|
ApOption apNone() { result = TAccessPathApproxNone() }
|
|
|
|
ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) }
|
|
|
|
import Level1CallContext
|
|
import LocalCallContext
|
|
|
|
predicate localStep(
|
|
NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue,
|
|
ApNil ap, Configuration config, LocalCc lcc
|
|
) {
|
|
localFlowBigStep(node1, state1, node2, state2, preservesValue, ap.getFront(), config, lcc)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
predicate flowOutOfCall(
|
|
DataFlowCall call, RetNodeEx node1, NodeEx node2, boolean allowsFieldFlow, Configuration config
|
|
) {
|
|
exists(FlowState state |
|
|
flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and
|
|
PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and
|
|
PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _,
|
|
pragma[only_bind_into](config))
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
predicate flowIntoCall(
|
|
DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow,
|
|
Configuration config
|
|
) {
|
|
exists(FlowState state |
|
|
flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and
|
|
PrevStage::revFlow(node2, pragma[only_bind_into](state), _, pragma[only_bind_into](config)) and
|
|
PrevStage::revFlowAlias(node1, pragma[only_bind_into](state), _,
|
|
pragma[only_bind_into](config))
|
|
)
|
|
}
|
|
|
|
bindingset[node, state, ap, config]
|
|
predicate filter(NodeEx node, FlowState state, Ap ap, Configuration config) { any() }
|
|
|
|
// Type checking is not necessary here as it has already been done in stage 3.
|
|
bindingset[ap, contentType]
|
|
predicate typecheckStore(Ap ap, DataFlowType contentType) { any() }
|
|
}
|
|
|
|
private module Stage4 = MkStage<Stage3>::Stage<Stage4Param>;
|
|
|
|
bindingset[conf, result]
|
|
private Configuration unbindConf(Configuration conf) {
|
|
exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c))
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate nodeMayUseSummary0(
|
|
NodeEx n, DataFlowCallable c, FlowState state, AccessPathApprox apa, Configuration config
|
|
) {
|
|
exists(AccessPathApprox apa0 |
|
|
Stage4::parameterMayFlowThrough(_, c, _, _) and
|
|
Stage4::revFlow(n, state, true, _, apa0, config) and
|
|
Stage4::fwdFlow(n, state, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and
|
|
n.getEnclosingCallable() = c
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate nodeMayUseSummary(
|
|
NodeEx n, FlowState state, AccessPathApprox apa, Configuration config
|
|
) {
|
|
exists(DataFlowCallable c |
|
|
Stage4::parameterMayFlowThrough(_, c, apa, config) and
|
|
nodeMayUseSummary0(n, c, state, apa, config)
|
|
)
|
|
}
|
|
|
|
private newtype TSummaryCtx =
|
|
TSummaryCtxNone() or
|
|
TSummaryCtxSome(ParamNodeEx p, FlowState state, AccessPath ap) {
|
|
exists(Configuration config |
|
|
Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), config) and
|
|
Stage4::revFlow(p, state, _, 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 ParamNodeEx p;
|
|
private FlowState s;
|
|
private AccessPath ap;
|
|
|
|
SummaryCtxSome() { this = TSummaryCtxSome(p, s, ap) }
|
|
|
|
ParameterPosition getParameterPos() { p.isParameterOf(_, result) }
|
|
|
|
ParamNodeEx getParamNode() { result = p }
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the number of length 2 access path approximations that correspond to `apa`.
|
|
*/
|
|
private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) {
|
|
exists(TypedContent tc, int len |
|
|
tc = apa.getHead() and
|
|
len = apa.len() and
|
|
result =
|
|
strictcount(AccessPathFront apf |
|
|
Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1),
|
|
config)
|
|
)
|
|
)
|
|
}
|
|
|
|
private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) {
|
|
result =
|
|
strictcount(NodeEx n, FlowState state |
|
|
Stage4::revFlow(n, state, apa, config) or nodeMayUseSummary(n, state, apa, config)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if a length 2 access path approximation matching `apa` is expected
|
|
* to be expensive.
|
|
*/
|
|
private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) {
|
|
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
|
aps = count1to2unfold(apa, config) and
|
|
nodes = countNodesUsingAccessPath(apa, config) and
|
|
accessPathCostLimits(apLimit, tupleLimit) and
|
|
apLimit < aps and
|
|
tupleLimit < (aps - 1) * nodes
|
|
)
|
|
}
|
|
|
|
private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) {
|
|
exists(TypedContent head |
|
|
apa.pop(head) = result and
|
|
Stage4::consCand(head, result, config)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds with `unfold = false` if a precise head-tail representation of `apa` is
|
|
* expected to be expensive. Holds with `unfold = true` otherwise.
|
|
*/
|
|
private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) {
|
|
if apa.getHead().forceHighPrecision()
|
|
then unfold = true
|
|
else
|
|
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
|
aps = countPotentialAps(apa, config) and
|
|
nodes = countNodesUsingAccessPath(apa, config) and
|
|
accessPathCostLimits(apLimit, tupleLimit) and
|
|
if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets the number of `AccessPath`s that correspond to `apa`.
|
|
*/
|
|
pragma[assume_small_delta]
|
|
private int countAps(AccessPathApprox apa, Configuration config) {
|
|
evalUnfold(apa, false, config) and
|
|
result = 1 and
|
|
(not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config))
|
|
or
|
|
evalUnfold(apa, false, config) and
|
|
result = count1to2unfold(apa, config) and
|
|
not expensiveLen1to2unfolding(apa, config)
|
|
or
|
|
evalUnfold(apa, true, config) and
|
|
result = countPotentialAps(apa, config)
|
|
}
|
|
|
|
/**
|
|
* Gets the number of `AccessPath`s that would correspond to `apa` assuming
|
|
* that it is expanded to a precise head-tail representation.
|
|
*/
|
|
language[monotonicAggregates]
|
|
pragma[assume_small_delta]
|
|
private int countPotentialAps(AccessPathApprox apa, Configuration config) {
|
|
apa instanceof AccessPathApproxNil and result = 1
|
|
or
|
|
result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config))
|
|
}
|
|
|
|
private newtype TAccessPath =
|
|
TAccessPathNil(DataFlowType t) or
|
|
TAccessPathCons(TypedContent head, AccessPath tail) {
|
|
exists(AccessPathApproxCons apa |
|
|
not evalUnfold(apa, false, _) and
|
|
head = apa.getHead() and
|
|
tail.getApprox() = getATail(apa, _)
|
|
)
|
|
} or
|
|
TAccessPathCons2(TypedContent head1, TypedContent head2, int len) {
|
|
exists(AccessPathApproxCons apa |
|
|
evalUnfold(apa, false, _) and
|
|
not expensiveLen1to2unfolding(apa, _) and
|
|
apa.len() = len and
|
|
head1 = apa.getHead() and
|
|
head2 = getATail(apa, _).getHead()
|
|
)
|
|
} or
|
|
TAccessPathCons1(TypedContent head, int len) {
|
|
exists(AccessPathApproxCons apa |
|
|
evalUnfold(apa, false, _) and
|
|
expensiveLen1to2unfolding(apa, _) and
|
|
apa.len() = len and
|
|
head = apa.getHead()
|
|
)
|
|
}
|
|
|
|
private newtype TPathNode =
|
|
pragma[assume_small_delta]
|
|
TPathNodeMid(
|
|
NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config
|
|
) {
|
|
// A PathNode is introduced by a source ...
|
|
Stage4::revFlow(node, state, config) and
|
|
sourceNode(node, state, config) and
|
|
(
|
|
if hasSourceCallCtx(config)
|
|
then cc instanceof CallContextSomeCall
|
|
else cc instanceof CallContextAny
|
|
) and
|
|
sc instanceof SummaryCtxNone and
|
|
ap = TAccessPathNil(node.getDataFlowType())
|
|
or
|
|
// ... or a step from an existing PathNode to another node.
|
|
exists(PathNodeMid mid |
|
|
pathStep(mid, node, state, cc, sc, ap) and
|
|
pragma[only_bind_into](config) = mid.getConfiguration() and
|
|
Stage4::revFlow(node, state, ap.getApprox(), pragma[only_bind_into](config))
|
|
)
|
|
} or
|
|
TPathNodeSink(NodeEx node, FlowState state, Configuration config) {
|
|
exists(PathNodeMid sink |
|
|
sink.isAtSink() and
|
|
node = sink.getNodeEx() and
|
|
state = sink.getState() and
|
|
config = sink.getConfiguration()
|
|
)
|
|
} or
|
|
TPathNodeSourceGroup(string sourceGroup, Configuration config) {
|
|
exists(PathNodeImpl source |
|
|
sourceGroup = source.getSourceGroup() and
|
|
config = source.getConfiguration()
|
|
)
|
|
} or
|
|
TPathNodeSinkGroup(string sinkGroup, Configuration config) {
|
|
exists(PathNodeSink sink |
|
|
sinkGroup = sink.getSinkGroup() and
|
|
config = sink.getConfiguration()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A list of `TypedContent`s followed by a `DataFlowType`. 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 AccessPath extends TAccessPath {
|
|
/** Gets the head of this access path, if any. */
|
|
abstract TypedContent getHead();
|
|
|
|
/** Gets the tail of this access path, if any. */
|
|
abstract AccessPath getTail();
|
|
|
|
/** Gets the front of this access path. */
|
|
abstract AccessPathFront getFront();
|
|
|
|
/** Gets the approximation of this access path. */
|
|
abstract AccessPathApprox getApprox();
|
|
|
|
/** Gets the length of this access path. */
|
|
abstract int length();
|
|
|
|
/** Gets a textual representation of this access path. */
|
|
abstract string toString();
|
|
|
|
/** Gets the access path obtained by popping `tc` from this access path, if any. */
|
|
final AccessPath pop(TypedContent tc) {
|
|
result = this.getTail() and
|
|
tc = this.getHead()
|
|
}
|
|
|
|
/** Gets the access path obtained by pushing `tc` onto this access path. */
|
|
final AccessPath push(TypedContent tc) { this = result.pop(tc) }
|
|
}
|
|
|
|
private class AccessPathNil extends AccessPath, TAccessPathNil {
|
|
private DataFlowType t;
|
|
|
|
AccessPathNil() { this = TAccessPathNil(t) }
|
|
|
|
DataFlowType getType() { result = t }
|
|
|
|
override TypedContent getHead() { none() }
|
|
|
|
override AccessPath getTail() { none() }
|
|
|
|
override AccessPathFrontNil getFront() { result = TFrontNil(t) }
|
|
|
|
override AccessPathApproxNil getApprox() { result = TNil(t) }
|
|
|
|
override int length() { result = 0 }
|
|
|
|
override string toString() { result = concat(": " + ppReprType(t)) }
|
|
}
|
|
|
|
private class AccessPathCons extends AccessPath, TAccessPathCons {
|
|
private TypedContent head;
|
|
private AccessPath tail;
|
|
|
|
AccessPathCons() { this = TAccessPathCons(head, tail) }
|
|
|
|
override TypedContent getHead() { result = head }
|
|
|
|
override AccessPath getTail() { result = tail }
|
|
|
|
override AccessPathFrontHead getFront() { result = TFrontHead(head) }
|
|
|
|
pragma[assume_small_delta]
|
|
override AccessPathApproxCons getApprox() {
|
|
result = TConsNil(head, tail.(AccessPathNil).getType())
|
|
or
|
|
result = TConsCons(head, tail.getHead(), this.length())
|
|
or
|
|
result = TCons1(head, this.length())
|
|
}
|
|
|
|
pragma[assume_small_delta]
|
|
override int length() { result = 1 + tail.length() }
|
|
|
|
private string toStringImpl(boolean needsSuffix) {
|
|
exists(DataFlowType t |
|
|
tail = TAccessPathNil(t) and
|
|
needsSuffix = false and
|
|
result = head.toString() + "]" + concat(" : " + ppReprType(t))
|
|
)
|
|
or
|
|
result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix)
|
|
or
|
|
exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) |
|
|
result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true
|
|
or
|
|
result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false
|
|
)
|
|
or
|
|
exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) |
|
|
result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true
|
|
or
|
|
result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false
|
|
)
|
|
}
|
|
|
|
override string toString() {
|
|
result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
|
|
or
|
|
result = "[" + this.toStringImpl(false)
|
|
}
|
|
}
|
|
|
|
private class AccessPathCons2 extends AccessPath, TAccessPathCons2 {
|
|
private TypedContent head1;
|
|
private TypedContent head2;
|
|
private int len;
|
|
|
|
AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) }
|
|
|
|
override TypedContent getHead() { result = head1 }
|
|
|
|
override AccessPath getTail() {
|
|
Stage4::consCand(head1, result.getApprox(), _) and
|
|
result.getHead() = head2 and
|
|
result.length() = len - 1
|
|
}
|
|
|
|
override AccessPathFrontHead getFront() { result = TFrontHead(head1) }
|
|
|
|
override AccessPathApproxCons getApprox() {
|
|
result = TConsCons(head1, head2, len) or
|
|
result = TCons1(head1, len)
|
|
}
|
|
|
|
override int length() { result = len }
|
|
|
|
override string toString() {
|
|
if len = 2
|
|
then result = "[" + head1.toString() + ", " + head2.toString() + "]"
|
|
else
|
|
result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]"
|
|
}
|
|
}
|
|
|
|
private class AccessPathCons1 extends AccessPath, TAccessPathCons1 {
|
|
private TypedContent head;
|
|
private int len;
|
|
|
|
AccessPathCons1() { this = TAccessPathCons1(head, len) }
|
|
|
|
override TypedContent getHead() { result = head }
|
|
|
|
override AccessPath getTail() {
|
|
Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1
|
|
}
|
|
|
|
override AccessPathFrontHead getFront() { result = TFrontHead(head) }
|
|
|
|
override AccessPathApproxCons getApprox() { result = TCons1(head, len) }
|
|
|
|
override int length() { result = len }
|
|
|
|
override string toString() {
|
|
if len = 1
|
|
then result = "[" + head.toString() + "]"
|
|
else result = "[" + head.toString() + ", ... (" + len.toString() + ")]"
|
|
}
|
|
}
|
|
|
|
abstract private class PathNodeImpl extends TPathNode {
|
|
/** Gets the `FlowState` of this node. */
|
|
abstract FlowState getState();
|
|
|
|
/** Gets the associated configuration. */
|
|
abstract Configuration getConfiguration();
|
|
|
|
/** Holds if this node is a source. */
|
|
abstract predicate isSource();
|
|
|
|
abstract PathNodeImpl getASuccessorImpl();
|
|
|
|
private PathNodeImpl getASuccessorIfHidden() {
|
|
this.isHidden() and
|
|
result = this.getASuccessorImpl()
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private PathNodeImpl getANonHiddenSuccessor0() {
|
|
result = this.getASuccessorIfHidden*() and
|
|
not result.isHidden()
|
|
}
|
|
|
|
final PathNodeImpl getANonHiddenSuccessor() {
|
|
result = this.getASuccessorImpl().getANonHiddenSuccessor0() and
|
|
not this.isHidden()
|
|
}
|
|
|
|
abstract NodeEx getNodeEx();
|
|
|
|
predicate isHidden() {
|
|
not this.getConfiguration().includeHiddenNodes() and
|
|
(
|
|
hiddenNode(this.getNodeEx().asNode()) and
|
|
not this.isSource() and
|
|
not this instanceof PathNodeSink
|
|
or
|
|
this.getNodeEx() instanceof TNodeImplicitRead
|
|
)
|
|
}
|
|
|
|
string getSourceGroup() {
|
|
this.isSource() and
|
|
this.getConfiguration().sourceGrouping(this.getNodeEx().asNode(), result)
|
|
}
|
|
|
|
predicate isFlowSource() {
|
|
this.isSource() and not exists(this.getSourceGroup())
|
|
or
|
|
this instanceof PathNodeSourceGroup
|
|
}
|
|
|
|
predicate isFlowSink() {
|
|
this = any(PathNodeSink sink | not exists(sink.getSinkGroup())) or
|
|
this instanceof PathNodeSinkGroup
|
|
}
|
|
|
|
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() + ">"
|
|
}
|
|
|
|
/** Gets a textual representation of this element. */
|
|
string toString() { result = this.getNodeEx().toString() + this.ppAp() }
|
|
|
|
/**
|
|
* Gets a textual representation of this element, including a textual
|
|
* representation of the call context.
|
|
*/
|
|
string toStringWithContext() { result = this.getNodeEx().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://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
|
*/
|
|
predicate hasLocationInfo(
|
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
|
) {
|
|
this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
|
}
|
|
}
|
|
|
|
/** Holds if `n` can reach a sink. */
|
|
private predicate directReach(PathNodeImpl n) {
|
|
n instanceof PathNodeSink or
|
|
n instanceof PathNodeSinkGroup or
|
|
directReach(n.getANonHiddenSuccessor())
|
|
}
|
|
|
|
/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */
|
|
private predicate reach(PathNodeImpl n) { directReach(n) or Subpaths::retReach(n) }
|
|
|
|
/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */
|
|
private predicate pathSucc(PathNodeImpl n1, PathNodeImpl n2) {
|
|
n1.getANonHiddenSuccessor() = n2 and directReach(n2)
|
|
}
|
|
|
|
private predicate pathSuccPlus(PathNodeImpl n1, PathNodeImpl n2) = fastTC(pathSucc/2)(n1, n2)
|
|
|
|
/**
|
|
* 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, and which can reach a sink, are generated.
|
|
*/
|
|
class PathNode instanceof PathNodeImpl {
|
|
PathNode() { reach(this) }
|
|
|
|
/** Gets a textual representation of this element. */
|
|
final string toString() { result = super.toString() }
|
|
|
|
/**
|
|
* Gets a textual representation of this element, including a textual
|
|
* representation of the call context.
|
|
*/
|
|
final string toStringWithContext() { result = super.toStringWithContext() }
|
|
|
|
/**
|
|
* Holds if this element is at the specified location.
|
|
* The location spans column `startcolumn` of line `startline` to
|
|
* column `endcolumn` of line `endline` in file `filepath`.
|
|
* For more information, see
|
|
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
|
*/
|
|
final predicate hasLocationInfo(
|
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
|
) {
|
|
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
|
}
|
|
|
|
/** Gets the underlying `Node`. */
|
|
final Node getNode() { super.getNodeEx().projectToNode() = result }
|
|
|
|
/** Gets the `FlowState` of this node. */
|
|
final FlowState getState() { result = super.getState() }
|
|
|
|
/** Gets the associated configuration. */
|
|
final Configuration getConfiguration() { result = super.getConfiguration() }
|
|
|
|
/** Gets a successor of this node, if any. */
|
|
final PathNode getASuccessor() { result = super.getANonHiddenSuccessor() }
|
|
|
|
/** Holds if this node is a source. */
|
|
final predicate isSource() { super.isSource() }
|
|
|
|
/** Holds if this node is a grouping of source nodes. */
|
|
final predicate isSourceGroup(string group) { this = TPathNodeSourceGroup(group, _) }
|
|
|
|
/** Holds if this node is a grouping of sink nodes. */
|
|
final predicate isSinkGroup(string group) { this = TPathNodeSinkGroup(group, _) }
|
|
}
|
|
|
|
/**
|
|
* 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) { a.getASuccessor() = b }
|
|
|
|
/** Holds if `n` is a node in the graph of data flow path explanations. */
|
|
query predicate nodes(PathNode n, string key, string val) {
|
|
key = "semmle.label" and val = n.toString()
|
|
}
|
|
|
|
/**
|
|
* Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through
|
|
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
|
|
* `ret -> out` is summarized as the edge `arg -> out`.
|
|
*/
|
|
query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) {
|
|
Subpaths::subpaths(arg, par, ret, out)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An intermediate flow graph node. This is a triple consisting of a `Node`,
|
|
* a `CallContext`, and a `Configuration`.
|
|
*/
|
|
private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
|
|
NodeEx node;
|
|
FlowState state;
|
|
CallContext cc;
|
|
SummaryCtx sc;
|
|
AccessPath ap;
|
|
Configuration config;
|
|
|
|
PathNodeMid() { this = TPathNodeMid(node, state, cc, sc, ap, config) }
|
|
|
|
override NodeEx getNodeEx() { result = node }
|
|
|
|
override FlowState getState() { result = state }
|
|
|
|
CallContext getCallContext() { result = cc }
|
|
|
|
SummaryCtx getSummaryCtx() { result = sc }
|
|
|
|
AccessPath getAp() { result = ap }
|
|
|
|
override Configuration getConfiguration() { result = config }
|
|
|
|
private PathNodeMid getSuccMid() {
|
|
pathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(),
|
|
result.getSummaryCtx(), result.getAp()) and
|
|
result.getConfiguration() = unbindConf(this.getConfiguration())
|
|
}
|
|
|
|
override PathNodeImpl getASuccessorImpl() {
|
|
// an intermediate step to another intermediate node
|
|
result = this.getSuccMid()
|
|
or
|
|
// a final step to a sink
|
|
result = this.getSuccMid().projectToSink()
|
|
}
|
|
|
|
override predicate isSource() {
|
|
sourceNode(node, state, config) and
|
|
(
|
|
if hasSourceCallCtx(config)
|
|
then cc instanceof CallContextSomeCall
|
|
else cc instanceof CallContextAny
|
|
) and
|
|
sc instanceof SummaryCtxNone and
|
|
ap = TAccessPathNil(node.getDataFlowType())
|
|
}
|
|
|
|
predicate isAtSink() {
|
|
sinkNode(node, state, config) and
|
|
ap instanceof AccessPathNil and
|
|
if hasSinkCallCtx(config)
|
|
then
|
|
// For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
|
|
// is exactly what we need to check. This also implies
|
|
// `sc instanceof SummaryCtxNone`.
|
|
// For `FeatureEqualSourceSinkCallContext` the initial call context was
|
|
// set to `CallContextSomeCall` and jumps are disallowed, so
|
|
// `cc instanceof CallContextNoCall` never holds. On the other hand,
|
|
// in this case there's never any need to enter a call except to identify
|
|
// a summary, so the condition in `pathIntoCallable` enforces this, which
|
|
// means that `sc instanceof SummaryCtxNone` holds if and only if we are
|
|
// in the call context of the source.
|
|
sc instanceof SummaryCtxNone or
|
|
cc instanceof CallContextNoCall
|
|
else any()
|
|
}
|
|
|
|
PathNodeSink projectToSink() {
|
|
this.isAtSink() and
|
|
result.getNodeEx() = node and
|
|
result.getState() = state and
|
|
result.getConfiguration() = unbindConf(config)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 {
|
|
NodeEx node;
|
|
FlowState state;
|
|
Configuration config;
|
|
|
|
PathNodeSink() { this = TPathNodeSink(node, state, config) }
|
|
|
|
override NodeEx getNodeEx() { result = node }
|
|
|
|
override FlowState getState() { result = state }
|
|
|
|
override Configuration getConfiguration() { result = config }
|
|
|
|
override PathNodeImpl getASuccessorImpl() {
|
|
result = TPathNodeSinkGroup(this.getSinkGroup(), config)
|
|
}
|
|
|
|
override predicate isSource() { sourceNode(node, state, config) }
|
|
|
|
string getSinkGroup() { config.sinkGrouping(node.asNode(), result) }
|
|
}
|
|
|
|
private class PathNodeSourceGroup extends PathNodeImpl, TPathNodeSourceGroup {
|
|
string sourceGroup;
|
|
Configuration config;
|
|
|
|
PathNodeSourceGroup() { this = TPathNodeSourceGroup(sourceGroup, config) }
|
|
|
|
override NodeEx getNodeEx() { none() }
|
|
|
|
override FlowState getState() { none() }
|
|
|
|
override Configuration getConfiguration() { result = config }
|
|
|
|
override PathNodeImpl getASuccessorImpl() {
|
|
result.getSourceGroup() = sourceGroup and
|
|
result.getConfiguration() = config
|
|
}
|
|
|
|
override predicate isSource() { none() }
|
|
|
|
override string toString() { result = sourceGroup }
|
|
|
|
override predicate hasLocationInfo(
|
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
|
) {
|
|
filepath = "" and startline = 0 and startcolumn = 0 and endline = 0 and endcolumn = 0
|
|
}
|
|
}
|
|
|
|
private class PathNodeSinkGroup extends PathNodeImpl, TPathNodeSinkGroup {
|
|
string sinkGroup;
|
|
Configuration config;
|
|
|
|
PathNodeSinkGroup() { this = TPathNodeSinkGroup(sinkGroup, config) }
|
|
|
|
override NodeEx getNodeEx() { none() }
|
|
|
|
override FlowState getState() { none() }
|
|
|
|
override Configuration getConfiguration() { result = config }
|
|
|
|
override PathNodeImpl getASuccessorImpl() { none() }
|
|
|
|
override predicate isSource() { none() }
|
|
|
|
override string toString() { result = sinkGroup }
|
|
|
|
override predicate hasLocationInfo(
|
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
|
) {
|
|
filepath = "" and startline = 0 and startcolumn = 0 and endline = 0 and endcolumn = 0
|
|
}
|
|
}
|
|
|
|
private predicate pathNode(
|
|
PathNodeMid mid, NodeEx midnode, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap,
|
|
Configuration conf, LocalCallContext localCC
|
|
) {
|
|
midnode = mid.getNodeEx() and
|
|
state = mid.getState() and
|
|
conf = mid.getConfiguration() and
|
|
cc = mid.getCallContext() and
|
|
sc = mid.getSummaryCtx() and
|
|
localCC =
|
|
getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)),
|
|
midnode.getEnclosingCallable()) and
|
|
ap = mid.getAp()
|
|
}
|
|
|
|
/**
|
|
* Holds if data may flow from `mid` to `node`. The last step in or out of
|
|
* a callable is recorded by `cc`.
|
|
*/
|
|
pragma[assume_small_delta]
|
|
pragma[nomagic]
|
|
private predicate pathStep(
|
|
PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, AccessPath ap
|
|
) {
|
|
exists(NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC |
|
|
pathNode(mid, midnode, state0, cc, sc, ap, conf, localCC) and
|
|
localFlowBigStep(midnode, state0, node, state, true, _, conf, localCC)
|
|
)
|
|
or
|
|
exists(
|
|
AccessPath ap0, NodeEx midnode, FlowState state0, Configuration conf, LocalCallContext localCC
|
|
|
|
|
pathNode(mid, midnode, state0, cc, sc, ap0, conf, localCC) and
|
|
localFlowBigStep(midnode, state0, node, state, false, ap.getFront(), conf, localCC) and
|
|
ap0 instanceof AccessPathNil
|
|
)
|
|
or
|
|
jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and
|
|
state = mid.getState() and
|
|
cc instanceof CallContextAny and
|
|
sc instanceof SummaryCtxNone and
|
|
ap = mid.getAp()
|
|
or
|
|
additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and
|
|
state = mid.getState() and
|
|
cc instanceof CallContextAny and
|
|
sc instanceof SummaryCtxNone and
|
|
mid.getAp() instanceof AccessPathNil and
|
|
ap = TAccessPathNil(node.getDataFlowType())
|
|
or
|
|
additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, mid.getConfiguration()) and
|
|
cc instanceof CallContextAny and
|
|
sc instanceof SummaryCtxNone and
|
|
mid.getAp() instanceof AccessPathNil and
|
|
ap = TAccessPathNil(node.getDataFlowType())
|
|
or
|
|
exists(TypedContent tc | pathStoreStep(mid, node, state, ap.pop(tc), tc, cc)) and
|
|
sc = mid.getSummaryCtx()
|
|
or
|
|
exists(TypedContent tc | pathReadStep(mid, node, state, ap.push(tc), tc, cc)) and
|
|
sc = mid.getSummaryCtx()
|
|
or
|
|
pathIntoCallable(mid, node, state, _, cc, sc, _, _) and ap = mid.getAp()
|
|
or
|
|
pathOutOfCallable(mid, node, state, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
|
|
or
|
|
pathThroughCallable(mid, node, state, cc, ap) and sc = mid.getSummaryCtx()
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate pathReadStep(
|
|
PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc
|
|
) {
|
|
ap0 = mid.getAp() and
|
|
tc = ap0.getHead() and
|
|
Stage4::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and
|
|
state = mid.getState() and
|
|
cc = mid.getCallContext()
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate pathStoreStep(
|
|
PathNodeMid mid, NodeEx node, FlowState state, AccessPath ap0, TypedContent tc, CallContext cc
|
|
) {
|
|
ap0 = mid.getAp() and
|
|
Stage4::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and
|
|
state = mid.getState() and
|
|
cc = mid.getCallContext()
|
|
}
|
|
|
|
private predicate pathOutOfCallable0(
|
|
PathNodeMid mid, ReturnPosition pos, FlowState state, CallContext innercc, AccessPathApprox apa,
|
|
Configuration config
|
|
) {
|
|
pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and
|
|
state = mid.getState() and
|
|
innercc = mid.getCallContext() and
|
|
innercc instanceof CallContextNoCall and
|
|
apa = mid.getAp().getApprox() and
|
|
config = mid.getConfiguration()
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate pathOutOfCallable1(
|
|
PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc,
|
|
AccessPathApprox apa, Configuration config
|
|
) {
|
|
exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc |
|
|
pathOutOfCallable0(mid, pos, state, innercc, apa, 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 NodeEx getAnOutNodeFlow(
|
|
ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config
|
|
) {
|
|
result.asNode() = kind.getAnOutNode(call) and
|
|
Stage4::revFlow(result, _, apa, 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, NodeEx out, FlowState state, CallContext cc) {
|
|
exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config |
|
|
pathOutOfCallable1(mid, call, kind, state, cc, apa, config) and
|
|
out = getAnOutNodeFlow(kind, call, apa, config)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`.
|
|
*/
|
|
pragma[noinline]
|
|
private predicate pathIntoArg(
|
|
PathNodeMid mid, ParameterPosition ppos, FlowState state, CallContext cc, DataFlowCall call,
|
|
AccessPath ap, AccessPathApprox apa, Configuration config
|
|
) {
|
|
exists(ArgNodeEx arg, ArgumentPosition apos |
|
|
pathNode(mid, arg, state, cc, _, ap, config, _) and
|
|
arg.asNode().(ArgNode).argumentOf(call, apos) and
|
|
apa = ap.getApprox() and
|
|
parameterMatch(ppos, apos)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate parameterCand(
|
|
DataFlowCallable callable, ParameterPosition pos, AccessPathApprox apa, Configuration config
|
|
) {
|
|
exists(ParamNodeEx p |
|
|
Stage4::revFlow(p, _, apa, config) and
|
|
p.isParameterOf(callable, pos)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate pathIntoCallable0(
|
|
PathNodeMid mid, DataFlowCallable callable, ParameterPosition pos, FlowState state,
|
|
CallContext outercc, DataFlowCall call, AccessPath ap, Configuration config
|
|
) {
|
|
exists(AccessPathApprox apa |
|
|
pathIntoArg(mid, pragma[only_bind_into](pos), state, outercc, call, ap,
|
|
pragma[only_bind_into](apa), pragma[only_bind_into](config)) and
|
|
callable = resolveCall(call, outercc) and
|
|
parameterCand(callable, pragma[only_bind_into](pos), pragma[only_bind_into](apa),
|
|
pragma[only_bind_into](config))
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if data may flow from `mid` to `p` through `call`. The contexts
|
|
* before and after entering the callable are `outercc` and `innercc`,
|
|
* respectively.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate pathIntoCallable(
|
|
PathNodeMid mid, ParamNodeEx p, FlowState state, CallContext outercc, CallContextCall innercc,
|
|
SummaryCtx sc, DataFlowCall call, Configuration config
|
|
) {
|
|
exists(ParameterPosition pos, DataFlowCallable callable, AccessPath ap |
|
|
pathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and
|
|
p.isParameterOf(callable, pos) and
|
|
(
|
|
sc = TSummaryCtxSome(p, state, ap)
|
|
or
|
|
not exists(TSummaryCtxSome(p, state, ap)) and
|
|
sc = TSummaryCtxNone() and
|
|
// When the call contexts of source and sink needs to match then there's
|
|
// never any reason to enter a callable except to find a summary. See also
|
|
// the comment in `PathNodeMid::isAtSink`.
|
|
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
|
)
|
|
|
|
|
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, FlowState state, CallContextCall cc, SummaryCtxSome sc, AccessPath ap,
|
|
AccessPathApprox apa, Configuration config
|
|
) {
|
|
exists(PathNodeMid mid, RetNodeEx ret, ParameterPosition pos |
|
|
pathNode(mid, ret, state, cc, sc, ap, config, _) and
|
|
kind = ret.getKind() and
|
|
apa = ap.getApprox() and
|
|
pos = sc.getParameterPos() and
|
|
// we don't expect a parameter to return stored in itself, unless explicitly allowed
|
|
(
|
|
not kind.(ParamUpdateReturnKind).getPosition() = pos
|
|
or
|
|
sc.getParamNode().allowParameterReturnInSelf()
|
|
)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate pathThroughCallable0(
|
|
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, FlowState state, CallContext cc,
|
|
AccessPath ap, AccessPathApprox apa, Configuration config
|
|
) {
|
|
exists(CallContext innercc, SummaryCtx sc |
|
|
pathIntoCallable(mid, _, _, cc, innercc, sc, call, config) and
|
|
paramFlowsThrough(kind, state, innercc, sc, ap, apa, config)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* 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, NodeEx out, FlowState state, CallContext cc, AccessPath ap
|
|
) {
|
|
exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
|
|
pathThroughCallable0(call, mid, kind, state, cc, ap, apa, config) and
|
|
out = getAnOutNodeFlow(kind, call, apa, config)
|
|
)
|
|
}
|
|
|
|
private module Subpaths {
|
|
/**
|
|
* Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by
|
|
* `kind`, `sc`, `apout`, and `innercc`.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate subpaths01(
|
|
PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
|
|
NodeEx out, FlowState sout, AccessPath apout
|
|
) {
|
|
exists(Configuration config |
|
|
pathThroughCallable(arg, out, pragma[only_bind_into](sout), _, pragma[only_bind_into](apout)) and
|
|
pathIntoCallable(arg, par, _, _, innercc, sc, _, config) and
|
|
paramFlowsThrough(kind, pragma[only_bind_into](sout), innercc, sc,
|
|
pragma[only_bind_into](apout), _, unbindConf(config)) and
|
|
not arg.isHidden()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by
|
|
* `kind`, `sc`, `sout`, `apout`, and `innercc`.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate subpaths02(
|
|
PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
|
|
NodeEx out, FlowState sout, AccessPath apout
|
|
) {
|
|
subpaths01(arg, par, sc, innercc, kind, out, sout, apout) and
|
|
out.asNode() = kind.getAnOutNode(_)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private Configuration getPathNodeConf(PathNodeImpl n) { result = n.getConfiguration() }
|
|
|
|
/**
|
|
* Holds if `(arg, par, ret, out)` forms a subpath-tuple.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate subpaths03(
|
|
PathNodeImpl arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, FlowState sout, AccessPath apout
|
|
) {
|
|
exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode |
|
|
subpaths02(arg, par, sc, innercc, kind, out, sout, apout) and
|
|
pathNode(ret, retnode, sout, innercc, sc, apout, unbindConf(getPathNodeConf(arg)), _) and
|
|
kind = retnode.getKind()
|
|
)
|
|
}
|
|
|
|
private PathNodeImpl localStepToHidden(PathNodeImpl n) {
|
|
n.getASuccessorImpl() = result and
|
|
result.isHidden() and
|
|
exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
|
|
localFlowBigStep(n1, _, n2, _, _, _, _, _) or
|
|
store(n1, _, n2, _, _) or
|
|
readSet(n1, _, n2, _)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) {
|
|
succ = pred.getANonHiddenSuccessor() and
|
|
succNode = succ.getNodeEx()
|
|
}
|
|
|
|
/**
|
|
* Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through
|
|
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
|
|
* `ret -> out` is summarized as the edge `arg -> out`.
|
|
*/
|
|
predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNodeImpl out) {
|
|
exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 |
|
|
pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and
|
|
subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and
|
|
hasSuccessor(pragma[only_bind_into](arg), par, p) and
|
|
not ret.isHidden() and
|
|
pathNode(out0, o, sout, _, _, apout, _, _)
|
|
|
|
|
out = out0 or out = out0.projectToSink()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `n` can reach a return node in a summarized subpath that can reach a sink.
|
|
*/
|
|
predicate retReach(PathNodeImpl n) {
|
|
exists(PathNodeImpl out | subpaths(_, _, n, out) | directReach(out) or retReach(out))
|
|
or
|
|
exists(PathNodeImpl mid |
|
|
retReach(mid) and
|
|
n.getANonHiddenSuccessor() = mid and
|
|
not subpaths(_, mid, _, _)
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 hasFlowPath(
|
|
PathNodeImpl flowsource, PathNodeImpl flowsink, Configuration configuration
|
|
) {
|
|
flowsource.isFlowSource() and
|
|
flowsource.getConfiguration() = configuration and
|
|
(flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and
|
|
flowsink.isFlowSink()
|
|
}
|
|
|
|
private predicate flowsTo(
|
|
PathNodeImpl flowsource, PathNodeSink flowsink, Node source, Node sink,
|
|
Configuration configuration
|
|
) {
|
|
flowsource.isSource() and
|
|
flowsource.getConfiguration() = configuration and
|
|
flowsource.getNodeEx().asNode() = source and
|
|
(flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and
|
|
flowsink.getNodeEx().asNode() = 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 predicate finalStats(
|
|
boolean fwd, int nodes, int fields, int conscand, int states, int tuples
|
|
) {
|
|
fwd = true and
|
|
nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and
|
|
fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and
|
|
conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and
|
|
states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state)) and
|
|
tuples = count(PathNodeImpl pn)
|
|
or
|
|
fwd = false and
|
|
nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and
|
|
fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and
|
|
conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and
|
|
states = count(FlowState state | exists(PathNodeMid pn | pn.getState() = state and reach(pn))) and
|
|
tuples = count(PathNode pn)
|
|
}
|
|
|
|
/**
|
|
* INTERNAL: Only for debugging.
|
|
*
|
|
* Calculates per-stage metrics for data flow.
|
|
*/
|
|
predicate stageStats(
|
|
int n, string stage, int nodes, int fields, int conscand, int states, int tuples,
|
|
Configuration config
|
|
) {
|
|
stage = "1 Fwd" and
|
|
n = 10 and
|
|
Stage1::stats(true, nodes, fields, conscand, states, tuples, config)
|
|
or
|
|
stage = "1 Rev" and
|
|
n = 15 and
|
|
Stage1::stats(false, nodes, fields, conscand, states, tuples, config)
|
|
or
|
|
stage = "2 Fwd" and
|
|
n = 20 and
|
|
Stage2::stats(true, nodes, fields, conscand, states, tuples, config)
|
|
or
|
|
stage = "2 Rev" and
|
|
n = 25 and
|
|
Stage2::stats(false, nodes, fields, conscand, states, tuples, config)
|
|
or
|
|
stage = "3 Fwd" and
|
|
n = 30 and
|
|
Stage3::stats(true, nodes, fields, conscand, states, tuples, config)
|
|
or
|
|
stage = "3 Rev" and
|
|
n = 35 and
|
|
Stage3::stats(false, nodes, fields, conscand, states, tuples, config)
|
|
or
|
|
stage = "4 Fwd" and
|
|
n = 40 and
|
|
Stage4::stats(true, nodes, fields, conscand, states, tuples, config)
|
|
or
|
|
stage = "4 Rev" and
|
|
n = 45 and
|
|
Stage4::stats(false, nodes, fields, conscand, states, tuples, config)
|
|
or
|
|
stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, states, tuples)
|
|
or
|
|
stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, states, tuples)
|
|
}
|
|
|
|
private module FlowExploration {
|
|
private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) {
|
|
exists(NodeEx node1, NodeEx node2 |
|
|
jumpStep(node1, node2, config)
|
|
or
|
|
additionalJumpStep(node1, node2, config)
|
|
or
|
|
additionalJumpStateStep(node1, _, node2, _, config)
|
|
or
|
|
// flow into callable
|
|
viableParamArgEx(_, node2, node1)
|
|
or
|
|
// flow out of a callable
|
|
viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2)
|
|
|
|
|
c1 = node1.getEnclosingCallable() and
|
|
c2 = node2.getEnclosingCallable() and
|
|
c1 != c2
|
|
)
|
|
}
|
|
|
|
private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) {
|
|
exists(Node n | config.isSource(n) or config.isSource(n, _) | c = getNodeEnclosingCallable(n))
|
|
or
|
|
exists(DataFlowCallable mid |
|
|
interestingCallableSrc(mid, config) and callableStep(mid, c, config)
|
|
)
|
|
}
|
|
|
|
private predicate interestingCallableSink(DataFlowCallable c, Configuration config) {
|
|
exists(Node n | config.isSink(n) or config.isSink(n, _) | c = getNodeEnclosingCallable(n))
|
|
or
|
|
exists(DataFlowCallable mid |
|
|
interestingCallableSink(mid, config) and callableStep(c, mid, config)
|
|
)
|
|
}
|
|
|
|
private newtype TCallableExt =
|
|
TCallable(DataFlowCallable c, Configuration config) {
|
|
interestingCallableSrc(c, config) or
|
|
interestingCallableSink(c, config)
|
|
} or
|
|
TCallableSrc() or
|
|
TCallableSink()
|
|
|
|
private predicate callableExtSrc(TCallableSrc src) { any() }
|
|
|
|
private predicate callableExtSink(TCallableSink sink) { any() }
|
|
|
|
private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) {
|
|
exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config |
|
|
callableStep(c1, c2, config) and
|
|
ce1 = TCallable(c1, pragma[only_bind_into](config)) and
|
|
ce2 = TCallable(c2, pragma[only_bind_into](config))
|
|
)
|
|
or
|
|
exists(Node n, Configuration config |
|
|
ce1 = TCallableSrc() and
|
|
(config.isSource(n) or config.isSource(n, _)) and
|
|
ce2 = TCallable(getNodeEnclosingCallable(n), config)
|
|
)
|
|
or
|
|
exists(Node n, Configuration config |
|
|
ce2 = TCallableSink() and
|
|
(config.isSink(n) or config.isSink(n, _)) and
|
|
ce1 = TCallable(getNodeEnclosingCallable(n), config)
|
|
)
|
|
}
|
|
|
|
private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) {
|
|
callableExtStepFwd(ce2, ce1)
|
|
}
|
|
|
|
private int distSrcExt(TCallableExt c) =
|
|
shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result)
|
|
|
|
private int distSinkExt(TCallableExt c) =
|
|
shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result)
|
|
|
|
private int distSrc(DataFlowCallable c, Configuration config) {
|
|
result = distSrcExt(TCallable(c, config)) - 1
|
|
}
|
|
|
|
private int distSink(DataFlowCallable c, Configuration config) {
|
|
result = distSinkExt(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())
|
|
}
|
|
}
|
|
|
|
private class PartialAccessPathNil extends PartialAccessPath, TPartialNil {
|
|
override string toString() {
|
|
exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(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() + ")]"
|
|
)
|
|
}
|
|
}
|
|
|
|
private newtype TRevPartialAccessPath =
|
|
TRevPartialNil() or
|
|
TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] }
|
|
|
|
/**
|
|
* Conceptually a list of `Content`s, but only the first
|
|
* element of the list and its length are tracked.
|
|
*/
|
|
private class RevPartialAccessPath extends TRevPartialAccessPath {
|
|
abstract string toString();
|
|
|
|
Content getHead() { this = TRevPartialCons(result, _) }
|
|
|
|
int len() {
|
|
this = TRevPartialNil() and result = 0
|
|
or
|
|
this = TRevPartialCons(_, result)
|
|
}
|
|
}
|
|
|
|
private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil {
|
|
override string toString() { result = "" }
|
|
}
|
|
|
|
private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons {
|
|
override string toString() {
|
|
exists(Content c, int len | this = TRevPartialCons(c, len) |
|
|
if len = 1
|
|
then result = "[" + c.toString() + "]"
|
|
else result = "[" + c.toString() + ", ... (" + len.toString() + ")]"
|
|
)
|
|
}
|
|
}
|
|
|
|
private predicate relevantState(FlowState state) {
|
|
sourceNode(_, state, _) or
|
|
sinkNode(_, state, _) or
|
|
additionalLocalStateStep(_, state, _, _, _) or
|
|
additionalLocalStateStep(_, _, _, state, _) or
|
|
additionalJumpStateStep(_, state, _, _, _) or
|
|
additionalJumpStateStep(_, _, _, state, _)
|
|
}
|
|
|
|
private newtype TSummaryCtx1 =
|
|
TSummaryCtx1None() or
|
|
TSummaryCtx1Param(ParamNodeEx p)
|
|
|
|
private newtype TSummaryCtx2 =
|
|
TSummaryCtx2None() or
|
|
TSummaryCtx2Some(FlowState s) { relevantState(s) }
|
|
|
|
private newtype TSummaryCtx3 =
|
|
TSummaryCtx3None() or
|
|
TSummaryCtx3Some(PartialAccessPath ap)
|
|
|
|
private newtype TRevSummaryCtx1 =
|
|
TRevSummaryCtx1None() or
|
|
TRevSummaryCtx1Some(ReturnPosition pos)
|
|
|
|
private newtype TRevSummaryCtx2 =
|
|
TRevSummaryCtx2None() or
|
|
TRevSummaryCtx2Some(FlowState s) { relevantState(s) }
|
|
|
|
private newtype TRevSummaryCtx3 =
|
|
TRevSummaryCtx3None() or
|
|
TRevSummaryCtx3Some(RevPartialAccessPath ap)
|
|
|
|
private newtype TPartialPathNode =
|
|
TPartialPathNodeFwd(
|
|
NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2,
|
|
TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config
|
|
) {
|
|
sourceNode(node, state, config) and
|
|
cc instanceof CallContextAny and
|
|
sc1 = TSummaryCtx1None() and
|
|
sc2 = TSummaryCtx2None() and
|
|
sc3 = TSummaryCtx3None() and
|
|
ap = TPartialNil(node.getDataFlowType()) and
|
|
exists(config.explorationLimit())
|
|
or
|
|
partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, ap, config) and
|
|
distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit()
|
|
} or
|
|
TPartialPathNodeRev(
|
|
NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, TRevSummaryCtx3 sc3,
|
|
RevPartialAccessPath ap, Configuration config
|
|
) {
|
|
sinkNode(node, state, config) and
|
|
sc1 = TRevSummaryCtx1None() and
|
|
sc2 = TRevSummaryCtx2None() and
|
|
sc3 = TRevSummaryCtx3None() and
|
|
ap = TRevPartialNil() and
|
|
exists(config.explorationLimit())
|
|
or
|
|
exists(PartialPathNodeRev mid |
|
|
revPartialPathStep(mid, node, state, sc1, sc2, sc3, ap, config) and
|
|
not clearsContentEx(node, ap.getHead()) and
|
|
(
|
|
notExpectsContent(node) or
|
|
expectsContentEx(node, ap.getHead())
|
|
) and
|
|
not fullBarrier(node, config) and
|
|
not stateBarrier(node, state, config) and
|
|
distSink(node.getEnclosingCallable(), config) <= config.explorationLimit()
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate partialPathNodeMk0(
|
|
NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2,
|
|
TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config
|
|
) {
|
|
exists(PartialPathNodeFwd mid |
|
|
partialPathStep(mid, node, state, cc, sc1, sc2, sc3, ap, config) and
|
|
not fullBarrier(node, config) and
|
|
not stateBarrier(node, state, config) and
|
|
not clearsContentEx(node, ap.getHead().getContent()) and
|
|
(
|
|
notExpectsContent(node) or
|
|
expectsContentEx(node, ap.getHead().getContent())
|
|
) and
|
|
if node.asNode() instanceof CastingNode
|
|
then compatibleTypes(node.getDataFlowType(), 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.getNodeEx().toString() + this.ppAp() }
|
|
|
|
/**
|
|
* Gets a textual representation of this element, including a textual
|
|
* representation of the call context.
|
|
*/
|
|
string toStringWithContext() {
|
|
result = this.getNodeEx().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://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
|
*/
|
|
predicate hasLocationInfo(
|
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
|
) {
|
|
this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
|
}
|
|
|
|
/** Gets the underlying `Node`. */
|
|
final Node getNode() { this.getNodeEx().projectToNode() = result }
|
|
|
|
FlowState getState() { none() }
|
|
|
|
private NodeEx getNodeEx() {
|
|
result = this.(PartialPathNodeFwd).getNodeEx() or
|
|
result = this.(PartialPathNodeRev).getNodeEx()
|
|
}
|
|
|
|
/** 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.getNodeEx().getEnclosingCallable(), this.getConfiguration())
|
|
}
|
|
|
|
/**
|
|
* Gets the approximate distance to the nearest sink measured in number
|
|
* of interprocedural steps.
|
|
*/
|
|
int getSinkDistance() {
|
|
result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration())
|
|
}
|
|
|
|
private string ppAp() {
|
|
exists(string s |
|
|
s = this.(PartialPathNodeFwd).getAp().toString() or
|
|
s = this.(PartialPathNodeRev).getAp().toString()
|
|
|
|
|
if s = "" then result = "" else result = " " + s
|
|
)
|
|
}
|
|
|
|
private string ppCtx() {
|
|
result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">"
|
|
}
|
|
|
|
/** Holds if this is a source in a forward-flow path. */
|
|
predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() }
|
|
|
|
/** Holds if this is a sink in a reverse-flow path. */
|
|
predicate isRevSink() { this.(PartialPathNodeRev).isSink() }
|
|
}
|
|
|
|
/**
|
|
* 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 PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd {
|
|
NodeEx node;
|
|
FlowState state;
|
|
CallContext cc;
|
|
TSummaryCtx1 sc1;
|
|
TSummaryCtx2 sc2;
|
|
TSummaryCtx3 sc3;
|
|
PartialAccessPath ap;
|
|
Configuration config;
|
|
|
|
PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, state, cc, sc1, sc2, sc3, ap, config) }
|
|
|
|
NodeEx getNodeEx() { result = node }
|
|
|
|
override FlowState getState() { result = state }
|
|
|
|
CallContext getCallContext() { result = cc }
|
|
|
|
TSummaryCtx1 getSummaryCtx1() { result = sc1 }
|
|
|
|
TSummaryCtx2 getSummaryCtx2() { result = sc2 }
|
|
|
|
TSummaryCtx3 getSummaryCtx3() { result = sc3 }
|
|
|
|
PartialAccessPath getAp() { result = ap }
|
|
|
|
override Configuration getConfiguration() { result = config }
|
|
|
|
override PartialPathNodeFwd getASuccessor() {
|
|
partialPathStep(this, result.getNodeEx(), result.getState(), result.getCallContext(),
|
|
result.getSummaryCtx1(), result.getSummaryCtx2(), result.getSummaryCtx3(), result.getAp(),
|
|
result.getConfiguration())
|
|
}
|
|
|
|
predicate isSource() {
|
|
sourceNode(node, state, config) and
|
|
cc instanceof CallContextAny and
|
|
sc1 = TSummaryCtx1None() and
|
|
sc2 = TSummaryCtx2None() and
|
|
sc3 = TSummaryCtx3None() and
|
|
ap instanceof TPartialNil
|
|
}
|
|
}
|
|
|
|
private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev {
|
|
NodeEx node;
|
|
FlowState state;
|
|
TRevSummaryCtx1 sc1;
|
|
TRevSummaryCtx2 sc2;
|
|
TRevSummaryCtx3 sc3;
|
|
RevPartialAccessPath ap;
|
|
Configuration config;
|
|
|
|
PartialPathNodeRev() { this = TPartialPathNodeRev(node, state, sc1, sc2, sc3, ap, config) }
|
|
|
|
NodeEx getNodeEx() { result = node }
|
|
|
|
override FlowState getState() { result = state }
|
|
|
|
TRevSummaryCtx1 getSummaryCtx1() { result = sc1 }
|
|
|
|
TRevSummaryCtx2 getSummaryCtx2() { result = sc2 }
|
|
|
|
TRevSummaryCtx3 getSummaryCtx3() { result = sc3 }
|
|
|
|
RevPartialAccessPath getAp() { result = ap }
|
|
|
|
override Configuration getConfiguration() { result = config }
|
|
|
|
override PartialPathNodeRev getASuccessor() {
|
|
revPartialPathStep(result, this.getNodeEx(), this.getState(), this.getSummaryCtx1(),
|
|
this.getSummaryCtx2(), this.getSummaryCtx3(), this.getAp(), this.getConfiguration())
|
|
}
|
|
|
|
predicate isSink() {
|
|
sinkNode(node, state, config) and
|
|
sc1 = TRevSummaryCtx1None() and
|
|
sc2 = TRevSummaryCtx2None() and
|
|
sc3 = TRevSummaryCtx3None() and
|
|
ap = TRevPartialNil()
|
|
}
|
|
}
|
|
|
|
private predicate partialPathStep(
|
|
PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1,
|
|
TSummaryCtx2 sc2, TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config
|
|
) {
|
|
not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and
|
|
(
|
|
localFlowStep(mid.getNodeEx(), node, config) and
|
|
state = mid.getState() and
|
|
cc = mid.getCallContext() and
|
|
sc1 = mid.getSummaryCtx1() and
|
|
sc2 = mid.getSummaryCtx2() and
|
|
sc3 = mid.getSummaryCtx3() and
|
|
ap = mid.getAp() and
|
|
config = mid.getConfiguration()
|
|
or
|
|
additionalLocalFlowStep(mid.getNodeEx(), node, config) and
|
|
state = mid.getState() and
|
|
cc = mid.getCallContext() and
|
|
sc1 = mid.getSummaryCtx1() and
|
|
sc2 = mid.getSummaryCtx2() and
|
|
sc3 = mid.getSummaryCtx3() and
|
|
mid.getAp() instanceof PartialAccessPathNil and
|
|
ap = TPartialNil(node.getDataFlowType()) and
|
|
config = mid.getConfiguration()
|
|
or
|
|
additionalLocalStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and
|
|
cc = mid.getCallContext() and
|
|
sc1 = mid.getSummaryCtx1() and
|
|
sc2 = mid.getSummaryCtx2() and
|
|
sc3 = mid.getSummaryCtx3() and
|
|
mid.getAp() instanceof PartialAccessPathNil and
|
|
ap = TPartialNil(node.getDataFlowType()) and
|
|
config = mid.getConfiguration()
|
|
)
|
|
or
|
|
jumpStep(mid.getNodeEx(), node, config) and
|
|
state = mid.getState() and
|
|
cc instanceof CallContextAny and
|
|
sc1 = TSummaryCtx1None() and
|
|
sc2 = TSummaryCtx2None() and
|
|
sc3 = TSummaryCtx3None() and
|
|
ap = mid.getAp() and
|
|
config = mid.getConfiguration()
|
|
or
|
|
additionalJumpStep(mid.getNodeEx(), node, config) and
|
|
state = mid.getState() and
|
|
cc instanceof CallContextAny and
|
|
sc1 = TSummaryCtx1None() and
|
|
sc2 = TSummaryCtx2None() and
|
|
sc3 = TSummaryCtx3None() and
|
|
mid.getAp() instanceof PartialAccessPathNil and
|
|
ap = TPartialNil(node.getDataFlowType()) and
|
|
config = mid.getConfiguration()
|
|
or
|
|
additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state, config) and
|
|
cc instanceof CallContextAny and
|
|
sc1 = TSummaryCtx1None() and
|
|
sc2 = TSummaryCtx2None() and
|
|
sc3 = TSummaryCtx3None() and
|
|
mid.getAp() instanceof PartialAccessPathNil and
|
|
ap = TPartialNil(node.getDataFlowType()) and
|
|
config = mid.getConfiguration()
|
|
or
|
|
partialPathStoreStep(mid, _, _, node, ap) and
|
|
state = mid.getState() and
|
|
cc = mid.getCallContext() and
|
|
sc1 = mid.getSummaryCtx1() and
|
|
sc2 = mid.getSummaryCtx2() and
|
|
sc3 = mid.getSummaryCtx3() and
|
|
config = mid.getConfiguration()
|
|
or
|
|
exists(PartialAccessPath ap0, TypedContent tc |
|
|
partialPathReadStep(mid, ap0, tc, node, cc, config) and
|
|
state = mid.getState() and
|
|
sc1 = mid.getSummaryCtx1() and
|
|
sc2 = mid.getSummaryCtx2() and
|
|
sc3 = mid.getSummaryCtx3() and
|
|
apConsFwd(ap, tc, ap0, config)
|
|
)
|
|
or
|
|
partialPathIntoCallable(mid, node, state, _, cc, sc1, sc2, sc3, _, ap, config)
|
|
or
|
|
partialPathOutOfCallable(mid, node, state, cc, ap, config) and
|
|
sc1 = TSummaryCtx1None() and
|
|
sc2 = TSummaryCtx2None() and
|
|
sc3 = TSummaryCtx3None()
|
|
or
|
|
partialPathThroughCallable(mid, node, state, cc, ap, config) and
|
|
sc1 = mid.getSummaryCtx1() and
|
|
sc2 = mid.getSummaryCtx2() and
|
|
sc3 = mid.getSummaryCtx3()
|
|
}
|
|
|
|
bindingset[result, i]
|
|
private int unbindInt(int i) { pragma[only_bind_out](i) = pragma[only_bind_out](result) }
|
|
|
|
pragma[inline]
|
|
private predicate partialPathStoreStep(
|
|
PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node,
|
|
PartialAccessPath ap2
|
|
) {
|
|
exists(NodeEx midNode, DataFlowType contentType |
|
|
midNode = mid.getNodeEx() and
|
|
ap1 = mid.getAp() and
|
|
store(midNode, tc, node, contentType, mid.getConfiguration()) 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(PartialPathNodeFwd mid |
|
|
partialPathStoreStep(mid, ap1, tc, _, ap2) and
|
|
config = mid.getConfiguration()
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate partialPathReadStep(
|
|
PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc,
|
|
Configuration config
|
|
) {
|
|
exists(NodeEx midNode |
|
|
midNode = mid.getNodeEx() and
|
|
ap = mid.getAp() and
|
|
read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and
|
|
ap.getHead() = tc and
|
|
pragma[only_bind_into](config) = mid.getConfiguration() and
|
|
cc = mid.getCallContext()
|
|
)
|
|
}
|
|
|
|
private predicate partialPathOutOfCallable0(
|
|
PartialPathNodeFwd mid, ReturnPosition pos, FlowState state, CallContext innercc,
|
|
PartialAccessPath ap, Configuration config
|
|
) {
|
|
pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and
|
|
state = mid.getState() and
|
|
innercc = mid.getCallContext() and
|
|
innercc instanceof CallContextNoCall and
|
|
ap = mid.getAp() and
|
|
config = mid.getConfiguration()
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate partialPathOutOfCallable1(
|
|
PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, FlowState state, CallContext cc,
|
|
PartialAccessPath ap, Configuration config
|
|
) {
|
|
exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc |
|
|
partialPathOutOfCallable0(mid, pos, state, 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(
|
|
PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap,
|
|
Configuration config
|
|
) {
|
|
exists(ReturnKindExt kind, DataFlowCall call |
|
|
partialPathOutOfCallable1(mid, call, kind, state, cc, ap, config)
|
|
|
|
|
out.asNode() = kind.getAnOutNode(call)
|
|
)
|
|
}
|
|
|
|
pragma[noinline]
|
|
private predicate partialPathIntoArg(
|
|
PartialPathNodeFwd mid, ParameterPosition ppos, FlowState state, CallContext cc,
|
|
DataFlowCall call, PartialAccessPath ap, Configuration config
|
|
) {
|
|
exists(ArgNode arg, ArgumentPosition apos |
|
|
arg = mid.getNodeEx().asNode() and
|
|
state = mid.getState() and
|
|
cc = mid.getCallContext() and
|
|
arg.argumentOf(call, apos) and
|
|
ap = mid.getAp() and
|
|
config = mid.getConfiguration() and
|
|
parameterMatch(ppos, apos)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate partialPathIntoCallable0(
|
|
PartialPathNodeFwd mid, DataFlowCallable callable, ParameterPosition pos, FlowState state,
|
|
CallContext outercc, DataFlowCall call, PartialAccessPath ap, Configuration config
|
|
) {
|
|
partialPathIntoArg(mid, pos, state, outercc, call, ap, config) and
|
|
callable = resolveCall(call, outercc)
|
|
}
|
|
|
|
private predicate partialPathIntoCallable(
|
|
PartialPathNodeFwd mid, ParamNodeEx p, FlowState state, CallContext outercc,
|
|
CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3,
|
|
DataFlowCall call, PartialAccessPath ap, Configuration config
|
|
) {
|
|
exists(ParameterPosition pos, DataFlowCallable callable |
|
|
partialPathIntoCallable0(mid, callable, pos, state, outercc, call, ap, config) and
|
|
p.isParameterOf(callable, pos) and
|
|
sc1 = TSummaryCtx1Param(p) and
|
|
sc2 = TSummaryCtx2Some(state) and
|
|
sc3 = TSummaryCtx3Some(ap)
|
|
|
|
|
if recordDataFlowCallSite(call, callable)
|
|
then innercc = TSpecificCall(call)
|
|
else innercc = TSomeCall()
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate paramFlowsThroughInPartialPath(
|
|
ReturnKindExt kind, FlowState state, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2,
|
|
TSummaryCtx3 sc3, PartialAccessPath ap, Configuration config
|
|
) {
|
|
exists(PartialPathNodeFwd mid, RetNodeEx ret |
|
|
mid.getNodeEx() = ret and
|
|
kind = ret.getKind() and
|
|
state = mid.getState() and
|
|
cc = mid.getCallContext() and
|
|
sc1 = mid.getSummaryCtx1() and
|
|
sc2 = mid.getSummaryCtx2() and
|
|
sc3 = mid.getSummaryCtx3() and
|
|
config = mid.getConfiguration() and
|
|
ap = mid.getAp()
|
|
)
|
|
}
|
|
|
|
pragma[noinline]
|
|
private predicate partialPathThroughCallable0(
|
|
DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, FlowState state, CallContext cc,
|
|
PartialAccessPath ap, Configuration config
|
|
) {
|
|
exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, TSummaryCtx3 sc3 |
|
|
partialPathIntoCallable(mid, _, _, cc, innercc, sc1, sc2, sc3, call, _, config) and
|
|
paramFlowsThroughInPartialPath(kind, state, innercc, sc1, sc2, sc3, ap, config)
|
|
)
|
|
}
|
|
|
|
private predicate partialPathThroughCallable(
|
|
PartialPathNodeFwd mid, NodeEx out, FlowState state, CallContext cc, PartialAccessPath ap,
|
|
Configuration config
|
|
) {
|
|
exists(DataFlowCall call, ReturnKindExt kind |
|
|
partialPathThroughCallable0(call, mid, kind, state, cc, ap, config) and
|
|
out.asNode() = kind.getAnOutNode(call)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate revPartialPathStep(
|
|
PartialPathNodeRev mid, NodeEx node, FlowState state, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2,
|
|
TRevSummaryCtx3 sc3, RevPartialAccessPath ap, Configuration config
|
|
) {
|
|
localFlowStep(node, mid.getNodeEx(), config) and
|
|
state = mid.getState() and
|
|
sc1 = mid.getSummaryCtx1() and
|
|
sc2 = mid.getSummaryCtx2() and
|
|
sc3 = mid.getSummaryCtx3() and
|
|
ap = mid.getAp() and
|
|
config = mid.getConfiguration()
|
|
or
|
|
additionalLocalFlowStep(node, mid.getNodeEx(), config) and
|
|
state = mid.getState() and
|
|
sc1 = mid.getSummaryCtx1() and
|
|
sc2 = mid.getSummaryCtx2() and
|
|
sc3 = mid.getSummaryCtx3() and
|
|
mid.getAp() instanceof RevPartialAccessPathNil and
|
|
ap = TRevPartialNil() and
|
|
config = mid.getConfiguration()
|
|
or
|
|
additionalLocalStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and
|
|
sc1 = mid.getSummaryCtx1() and
|
|
sc2 = mid.getSummaryCtx2() and
|
|
sc3 = mid.getSummaryCtx3() and
|
|
mid.getAp() instanceof RevPartialAccessPathNil and
|
|
ap = TRevPartialNil() and
|
|
config = mid.getConfiguration()
|
|
or
|
|
jumpStep(node, mid.getNodeEx(), config) and
|
|
state = mid.getState() and
|
|
sc1 = TRevSummaryCtx1None() and
|
|
sc2 = TRevSummaryCtx2None() and
|
|
sc3 = TRevSummaryCtx3None() and
|
|
ap = mid.getAp() and
|
|
config = mid.getConfiguration()
|
|
or
|
|
additionalJumpStep(node, mid.getNodeEx(), config) and
|
|
state = mid.getState() and
|
|
sc1 = TRevSummaryCtx1None() and
|
|
sc2 = TRevSummaryCtx2None() and
|
|
sc3 = TRevSummaryCtx3None() and
|
|
mid.getAp() instanceof RevPartialAccessPathNil and
|
|
ap = TRevPartialNil() and
|
|
config = mid.getConfiguration()
|
|
or
|
|
additionalJumpStateStep(node, state, mid.getNodeEx(), mid.getState(), config) and
|
|
sc1 = TRevSummaryCtx1None() and
|
|
sc2 = TRevSummaryCtx2None() and
|
|
sc3 = TRevSummaryCtx3None() and
|
|
mid.getAp() instanceof RevPartialAccessPathNil and
|
|
ap = TRevPartialNil() and
|
|
config = mid.getConfiguration()
|
|
or
|
|
revPartialPathReadStep(mid, _, _, node, ap) and
|
|
state = mid.getState() and
|
|
sc1 = mid.getSummaryCtx1() and
|
|
sc2 = mid.getSummaryCtx2() and
|
|
sc3 = mid.getSummaryCtx3() and
|
|
config = mid.getConfiguration()
|
|
or
|
|
exists(RevPartialAccessPath ap0, Content c |
|
|
revPartialPathStoreStep(mid, ap0, c, node, config) and
|
|
state = mid.getState() and
|
|
sc1 = mid.getSummaryCtx1() and
|
|
sc2 = mid.getSummaryCtx2() and
|
|
sc3 = mid.getSummaryCtx3() and
|
|
apConsRev(ap, c, ap0, config)
|
|
)
|
|
or
|
|
exists(ParamNodeEx p |
|
|
mid.getNodeEx() = p and
|
|
viableParamArgEx(_, p, node) and
|
|
state = mid.getState() and
|
|
sc1 = mid.getSummaryCtx1() and
|
|
sc2 = mid.getSummaryCtx2() and
|
|
sc3 = mid.getSummaryCtx3() and
|
|
sc1 = TRevSummaryCtx1None() and
|
|
sc2 = TRevSummaryCtx2None() and
|
|
sc3 = TRevSummaryCtx3None() and
|
|
ap = mid.getAp() and
|
|
config = mid.getConfiguration()
|
|
)
|
|
or
|
|
exists(ReturnPosition pos |
|
|
revPartialPathIntoReturn(mid, pos, state, sc1, sc2, sc3, _, ap, config) and
|
|
pos = getReturnPosition(node.asNode())
|
|
)
|
|
or
|
|
revPartialPathThroughCallable(mid, node, state, ap, config) and
|
|
sc1 = mid.getSummaryCtx1() and
|
|
sc2 = mid.getSummaryCtx2() and
|
|
sc3 = mid.getSummaryCtx3()
|
|
}
|
|
|
|
pragma[inline]
|
|
private predicate revPartialPathReadStep(
|
|
PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node,
|
|
RevPartialAccessPath ap2
|
|
) {
|
|
exists(NodeEx midNode |
|
|
midNode = mid.getNodeEx() and
|
|
ap1 = mid.getAp() and
|
|
read(node, c, midNode, mid.getConfiguration()) and
|
|
ap2.getHead() = c and
|
|
ap2.len() = unbindInt(ap1.len() + 1)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate apConsRev(
|
|
RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config
|
|
) {
|
|
exists(PartialPathNodeRev mid |
|
|
revPartialPathReadStep(mid, ap1, c, _, ap2) and
|
|
config = mid.getConfiguration()
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate revPartialPathStoreStep(
|
|
PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config
|
|
) {
|
|
exists(NodeEx midNode, TypedContent tc |
|
|
midNode = mid.getNodeEx() and
|
|
ap = mid.getAp() and
|
|
store(node, tc, midNode, _, config) and
|
|
ap.getHead() = c and
|
|
config = mid.getConfiguration() and
|
|
tc.getContent() = c
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate revPartialPathIntoReturn(
|
|
PartialPathNodeRev mid, ReturnPosition pos, FlowState state, TRevSummaryCtx1Some sc1,
|
|
TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3, DataFlowCall call, RevPartialAccessPath ap,
|
|
Configuration config
|
|
) {
|
|
exists(NodeEx out |
|
|
mid.getNodeEx() = out and
|
|
mid.getState() = state and
|
|
viableReturnPosOutEx(call, pos, out) and
|
|
sc1 = TRevSummaryCtx1Some(pos) and
|
|
sc2 = TRevSummaryCtx2Some(state) and
|
|
sc3 = TRevSummaryCtx3Some(ap) and
|
|
ap = mid.getAp() and
|
|
config = mid.getConfiguration()
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate revPartialPathFlowsThrough(
|
|
ArgumentPosition apos, FlowState state, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2,
|
|
TRevSummaryCtx3Some sc3, RevPartialAccessPath ap, Configuration config
|
|
) {
|
|
exists(PartialPathNodeRev mid, ParamNodeEx p, ParameterPosition ppos |
|
|
mid.getNodeEx() = p and
|
|
mid.getState() = state and
|
|
p.getPosition() = ppos and
|
|
sc1 = mid.getSummaryCtx1() and
|
|
sc2 = mid.getSummaryCtx2() and
|
|
sc3 = mid.getSummaryCtx3() and
|
|
ap = mid.getAp() and
|
|
config = mid.getConfiguration() and
|
|
parameterMatch(ppos, apos)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate revPartialPathThroughCallable0(
|
|
DataFlowCall call, PartialPathNodeRev mid, ArgumentPosition pos, FlowState state,
|
|
RevPartialAccessPath ap, Configuration config
|
|
) {
|
|
exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, TRevSummaryCtx3Some sc3 |
|
|
revPartialPathIntoReturn(mid, _, _, sc1, sc2, sc3, call, _, config) and
|
|
revPartialPathFlowsThrough(pos, state, sc1, sc2, sc3, ap, config)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate revPartialPathThroughCallable(
|
|
PartialPathNodeRev mid, ArgNodeEx node, FlowState state, RevPartialAccessPath ap,
|
|
Configuration config
|
|
) {
|
|
exists(DataFlowCall call, ArgumentPosition pos |
|
|
revPartialPathThroughCallable0(call, mid, pos, state, ap, config) and
|
|
node.asNode().(ArgNode).argumentOf(call, pos)
|
|
)
|
|
}
|
|
}
|
|
|
|
import FlowExploration
|
|
|
|
private predicate partialFlow(
|
|
PartialPathNode source, PartialPathNode node, Configuration configuration
|
|
) {
|
|
source.getConfiguration() = configuration and
|
|
source.isFwdSource() and
|
|
node = source.getASuccessor+()
|
|
}
|
|
|
|
private predicate revPartialFlow(
|
|
PartialPathNode node, PartialPathNode sink, Configuration configuration
|
|
) {
|
|
sink.getConfiguration() = configuration and
|
|
sink.isRevSink() and
|
|
node.getASuccessor+() = sink
|
|
}
|