Merge pull request #12186 from aschackmull/dataflow/refactor-configuration

Data flow: Refactor configuration
This commit is contained in:
Anders Schack-Mulligen
2023-03-06 13:38:59 +01:00
committed by GitHub
156 changed files with 46756 additions and 184499 deletions

View File

@@ -0,0 +1,9 @@
---
category: majorAnalysis
---
* The main data flow and taint tracking APIs have been changed. The old APIs
remain in place for now and translate to the new through a
backwards-compatible wrapper. If multiple configurations are in scope
simultaneously, then this may affect results slightly. The new API is quite
similar to the old, but makes use of a configuration module instead of a
configuration class.

View File

@@ -6,5 +6,6 @@
import java
module DataFlow {
import semmle.code.java.dataflow.internal.DataFlowImpl
import semmle.code.java.dataflow.internal.DataFlow
import semmle.code.java.dataflow.internal.DataFlowImpl1
}

View File

@@ -8,5 +8,6 @@ import semmle.code.java.dataflow.DataFlow2
import semmle.code.java.dataflow.internal.TaintTrackingUtil::StringBuilderVarModule
module TaintTracking {
import semmle.code.java.dataflow.internal.tainttracking1.TaintTracking
import semmle.code.java.dataflow.internal.tainttracking1.TaintTrackingImpl
}

View File

@@ -0,0 +1,245 @@
/**
* 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 `Make` and `MakeWithState` modules.
*/
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
import DataFlowImplCommonPublic
private import DataFlowImpl
/** An input configuration for data flow. */
signature module ConfigSig {
/**
* Holds if `source` is a relevant data flow source.
*/
predicate isSource(Node source);
/**
* Holds if `sink` is a relevant data flow sink.
*/
predicate isSink(Node sink);
/**
* Holds if data flow through `node` is prohibited. This completely removes
* `node` from the data flow graph.
*/
default predicate isBarrier(Node node) { none() }
/** Holds if data flow into `node` is prohibited. */
default predicate isBarrierIn(Node node) { none() }
/** Holds if data flow out of `node` is prohibited. */
default predicate isBarrierOut(Node node) { none() }
/**
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
*/
default predicate isAdditionalFlowStep(Node node1, Node node2) { none() }
/**
* Holds if an arbitrary number of implicit read steps of content `c` may be
* taken at `node`.
*/
default predicate allowImplicitRead(Node node, ContentSet c) { none() }
/**
* Gets the virtual dispatch branching limit when calculating field flow.
* This can be overridden to a smaller value to improve performance (a
* value of 0 disables field flow), or a larger value to get more results.
*/
default int fieldFlowBranchLimit() { result = 2 }
/**
* Gets a data flow configuration feature to add restrictions to the set of
* valid flow paths.
*
* - `FeatureHasSourceCallContext`:
* Assume that sources have some existing call context to disallow
* conflicting return-flow directly following the source.
* - `FeatureHasSinkCallContext`:
* Assume that sinks have some existing call context to disallow
* conflicting argument-to-parameter flow directly preceding the sink.
* - `FeatureEqualSourceSinkCallContext`:
* Implies both of the above and additionally ensures that the entire flow
* path preserves the call context.
*
* These features are generally not relevant for typical end-to-end data flow
* queries, but should only be used for constructing paths that need to
* somehow be pluggable in another path context.
*/
default FlowFeature getAFeature() { none() }
/** Holds if sources should be grouped in the result of `hasFlowPath`. */
default predicate sourceGrouping(Node source, string sourceGroup) { none() }
/** Holds if sinks should be grouped in the result of `hasFlowPath`. */
default predicate sinkGrouping(Node sink, string sinkGroup) { none() }
/**
* Holds if hidden nodes should be included in the data flow graph.
*
* This feature should only be used for debugging or when the data flow graph
* is not visualized (as it is in a `path-problem` query).
*/
default predicate includeHiddenNodes() { none() }
}
/** An input configuration for data flow using flow state. */
signature module StateConfigSig {
bindingset[this]
class FlowState;
/**
* Holds if `source` is a relevant data flow source with the given initial
* `state`.
*/
predicate isSource(Node source, FlowState state);
/**
* Holds if `sink` is a relevant data flow sink accepting `state`.
*/
predicate isSink(Node sink, FlowState state);
/**
* Holds if data flow through `node` is prohibited. This completely removes
* `node` from the data flow graph.
*/
default predicate isBarrier(Node node) { none() }
/**
* Holds if data flow through `node` is prohibited when the flow state is
* `state`.
*/
predicate isBarrier(Node node, FlowState state);
/** Holds if data flow into `node` is prohibited. */
default predicate isBarrierIn(Node node) { none() }
/** Holds if data flow out of `node` is prohibited. */
default predicate isBarrierOut(Node node) { none() }
/**
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
*/
default predicate isAdditionalFlowStep(Node node1, Node node2) { none() }
/**
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
* This step is only applicable in `state1` and updates the flow state to `state2`.
*/
predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2);
/**
* Holds if an arbitrary number of implicit read steps of content `c` may be
* taken at `node`.
*/
default predicate allowImplicitRead(Node node, ContentSet c) { none() }
/**
* Gets the virtual dispatch branching limit when calculating field flow.
* This can be overridden to a smaller value to improve performance (a
* value of 0 disables field flow), or a larger value to get more results.
*/
default int fieldFlowBranchLimit() { result = 2 }
/**
* Gets a data flow configuration feature to add restrictions to the set of
* valid flow paths.
*
* - `FeatureHasSourceCallContext`:
* Assume that sources have some existing call context to disallow
* conflicting return-flow directly following the source.
* - `FeatureHasSinkCallContext`:
* Assume that sinks have some existing call context to disallow
* conflicting argument-to-parameter flow directly preceding the sink.
* - `FeatureEqualSourceSinkCallContext`:
* Implies both of the above and additionally ensures that the entire flow
* path preserves the call context.
*
* These features are generally not relevant for typical end-to-end data flow
* queries, but should only be used for constructing paths that need to
* somehow be pluggable in another path context.
*/
default FlowFeature getAFeature() { none() }
/** Holds if sources should be grouped in the result of `hasFlowPath`. */
default predicate sourceGrouping(Node source, string sourceGroup) { none() }
/** Holds if sinks should be grouped in the result of `hasFlowPath`. */
default predicate sinkGrouping(Node sink, string sinkGroup) { none() }
/**
* Holds if hidden nodes should be included in the data flow graph.
*
* This feature should only be used for debugging or when the data flow graph
* is not visualized (as it is in a `path-problem` query).
*/
default predicate includeHiddenNodes() { none() }
}
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
* measured in approximate number of interprocedural steps.
*/
signature int explorationLimitSig();
/**
* The output of a data flow computation.
*/
signature module DataFlowSig {
/**
* A `Node` augmented with a call context (except for sinks) and an access path.
* Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated.
*/
class PathNode;
/**
* Holds if data can flow from `source` to `sink`.
*
* The corresponding paths are generated from the end-points and the graph
* included in the module `PathGraph`.
*/
predicate hasFlowPath(PathNode source, PathNode sink);
/**
* Holds if data can flow from `source` to `sink`.
*/
predicate hasFlow(Node source, Node sink);
/**
* Holds if data can flow from some source to `sink`.
*/
predicate hasFlowTo(Node sink);
/**
* Holds if data can flow from some source to `sink`.
*/
predicate hasFlowToExpr(DataFlowExpr sink);
}
/**
* Constructs a standard data flow computation.
*/
module Make<ConfigSig Config> implements DataFlowSig {
private module C implements FullStateConfigSig {
import DefaultState<Config>
import Config
}
import Impl<C>
}
/**
* Constructs a data flow computation using flow state.
*/
module MakeWithState<StateConfigSig Config> implements DataFlowSig {
private module C implements FullStateConfigSig {
import Config
}
import Impl<C>
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,396 @@
/**
* DEPRECATED: Use `Make` and `MakeWithState` instead.
*
* Provides a `Configuration` class backwards-compatible interface to the data
* flow library.
*/
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
private import DataFlowImpl
import DataFlowImplCommonPublic
import FlowStateString
/**
* 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 sink, 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.
*
* These features are generally not relevant for typical end-to-end data flow
* queries, but should only be used for constructing paths that need to
* somehow be pluggable in another path context.
*/
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) { hasFlow(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) { hasFlowTo(sink, this) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* DEPRECATED: Use `FlowExploration<explorationLimit>` instead.
*
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
* measured in approximate number of interprocedural steps.
*/
deprecated 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() }
}
/**
* 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)
}
}
/** 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()
)
}
}
private FlowState relevantState(Configuration config) {
config.isSource(_, result) or
config.isSink(_, result) or
config.isBarrier(_, result) or
config.isAdditionalFlowStep(_, result, _, _) or
config.isAdditionalFlowStep(_, _, _, result)
}
private newtype TConfigState =
TMkConfigState(Configuration config, FlowState state) {
state = relevantState(config) or state instanceof FlowStateEmpty
}
private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) }
private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) }
private predicate singleConfiguration() { 1 = strictcount(Configuration c) }
private module Config implements FullStateConfigSig {
class FlowState = TConfigState;
predicate isSource(Node source, FlowState state) {
getConfig(state).isSource(source, getState(state))
or
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
}
predicate isSink(Node sink, FlowState state) {
getConfig(state).isSink(sink, getState(state))
or
getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty
}
predicate isBarrier(Node node) { none() }
predicate isBarrier(Node node, FlowState state) {
getConfig(state).isBarrier(node, getState(state)) or
getConfig(state).isBarrier(node) or
any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or
any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state))
}
predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) }
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
predicate isAdditionalFlowStep(Node node1, Node node2) {
singleConfiguration() and
any(Configuration config).isAdditionalFlowStep(node1, node2)
}
predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) {
getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and
getConfig(state2) = getConfig(state1)
or
not singleConfiguration() and
getConfig(state1).isAdditionalFlowStep(node1, node2) and
state2 = state1
}
predicate allowImplicitRead(Node node, ContentSet c) {
any(Configuration config).allowImplicitRead(node, c)
}
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }
predicate sourceGrouping(Node source, string sourceGroup) {
any(Configuration config).sourceGrouping(source, sourceGroup)
}
predicate sinkGrouping(Node sink, string sinkGroup) {
any(Configuration config).sinkGrouping(sink, sinkGroup)
}
predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() }
}
private import Impl<Config> as I
import I
/**
* 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 I::PathNode {
/** 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() { result = super.getNode() }
/** Gets the `FlowState` of this node. */
final FlowState getState() { result = getState(super.getState()) }
/** Gets the associated configuration. */
final Configuration getConfiguration() { result = getConfig(super.getState()) }
/** Gets a successor of this node, if any. */
final PathNode getASuccessor() { result = super.getASuccessor() }
/** 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) { super.isSourceGroup(group) }
/** Holds if this node is a grouping of sink nodes. */
final predicate isSinkGroup(string group) { super.isSinkGroup(group) }
}
private predicate hasFlow(Node source, Node sink, Configuration config) {
exists(PathNode source0, PathNode sink0 |
hasFlowPath(source0, sink0, config) and
source0.getNode() = source and
sink0.getNode() = sink
)
}
private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) {
hasFlowPath(source, sink) and source.getConfiguration() = config
}
private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) }
predicate flowsTo = hasFlow/3;

View File

@@ -3,15 +3,18 @@ private import DataFlowImplSpecific::Public
import Cached
module DataFlowImplCommonPublic {
/** A state value to track during data flow. */
class FlowState = string;
/** Provides `FlowState = string`. */
module FlowStateString {
/** A state value to track during data flow. */
class FlowState = string;
/**
* The default state, which is used when the state is unspecified for a source
* or a sink.
*/
class FlowStateEmpty extends FlowState {
FlowStateEmpty() { this = "" }
/**
* The default state, which is used when the state is unspecified for a source
* or a sink.
*/
class FlowStateEmpty extends FlowState {
FlowStateEmpty() { this = "" }
}
}
private newtype TFlowFeature =

View File

@@ -0,0 +1,63 @@
/**
* Provides classes for performing local (intra-procedural) and
* global (inter-procedural) taint-tracking analyses.
*/
import TaintTrackingParameter::Public
private import TaintTrackingParameter::Private
private module AddTaintDefaults<DataFlowInternal::FullStateConfigSig Config> implements
DataFlowInternal::FullStateConfigSig {
import Config
predicate isBarrier(DataFlow::Node node) {
Config::isBarrier(node) or defaultTaintSanitizer(node)
}
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
Config::isAdditionalFlowStep(node1, node2) or
defaultAdditionalTaintStep(node1, node2)
}
predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) {
Config::allowImplicitRead(node, c)
or
(
Config::isSink(node, _) or
Config::isAdditionalFlowStep(node, _) or
Config::isAdditionalFlowStep(node, _, _, _)
) and
defaultImplicitTaintRead(node, c)
}
}
/**
* Constructs a standard taint tracking computation.
*/
module Make<DataFlow::ConfigSig Config> implements DataFlow::DataFlowSig {
private module Config0 implements DataFlowInternal::FullStateConfigSig {
import DataFlowInternal::DefaultState<Config>
import Config
}
private module C implements DataFlowInternal::FullStateConfigSig {
import AddTaintDefaults<Config0>
}
import DataFlowInternal::Impl<C>
}
/**
* Constructs a taint tracking computation using flow state.
*/
module MakeWithState<DataFlow::StateConfigSig Config> implements DataFlow::DataFlowSig {
private module Config0 implements DataFlowInternal::FullStateConfigSig {
import Config
}
private module C implements DataFlowInternal::FullStateConfigSig {
import AddTaintDefaults<Config0>
}
import DataFlowInternal::Impl<C>
}

View File

@@ -2,4 +2,5 @@ import semmle.code.java.dataflow.internal.TaintTrackingUtil as Public
module Private {
import semmle.code.java.dataflow.DataFlow::DataFlow as DataFlow
import semmle.code.java.dataflow.internal.DataFlowImpl as DataFlowInternal
}

View File

@@ -47,6 +47,20 @@ private predicate defaultSource(DataFlow::Node src) {
src.asExpr().(MethodAccess).getMethod().getName() = ["source", "taint"]
}
module DefaultFlowConf implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node n) { defaultSource(n) }
predicate isSink(DataFlow::Node n) {
exists(MethodAccess ma | ma.getMethod().hasName("sink") | n.asExpr() = ma.getAnArgument())
}
int fieldFlowBranchLimit() { result = 1000 }
}
private module DefaultValueFlow = DataFlow::Make<DefaultFlowConf>;
private module DefaultTaintFlow = TaintTracking::Make<DefaultFlowConf>;
class DefaultValueFlowConf extends DataFlow::Configuration {
DefaultValueFlowConf() { this = "qltest:defaultValueFlowConf" }
@@ -76,6 +90,8 @@ private string getSourceArgString(DataFlow::Node src) {
src.asExpr().(MethodAccess).getAnArgument().(StringLiteral).getValue() = result
}
abstract class EnableLegacyConfiguration extends Unit { }
class InlineFlowTest extends InlineExpectationsTest {
InlineFlowTest() { this = "HasFlowTest" }
@@ -83,7 +99,7 @@ class InlineFlowTest extends InlineExpectationsTest {
override predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "hasValueFlow" and
exists(DataFlow::Node src, DataFlow::Node sink | getValueFlowConfig().hasFlow(src, sink) |
exists(DataFlow::Node src, DataFlow::Node sink | hasValueFlow(src, sink) |
sink.getLocation() = location and
element = sink.toString() and
if exists(getSourceArgString(src)) then value = getSourceArgString(src) else value = ""
@@ -91,7 +107,7 @@ class InlineFlowTest extends InlineExpectationsTest {
or
tag = "hasTaintFlow" and
exists(DataFlow::Node src, DataFlow::Node sink |
getTaintFlowConfig().hasFlow(src, sink) and not getValueFlowConfig().hasFlow(src, sink)
hasTaintFlow(src, sink) and not hasValueFlow(src, sink)
|
sink.getLocation() = location and
element = sink.toString() and
@@ -99,6 +115,18 @@ class InlineFlowTest extends InlineExpectationsTest {
)
}
predicate hasValueFlow(DataFlow::Node src, DataFlow::Node sink) {
if exists(EnableLegacyConfiguration e)
then getValueFlowConfig().hasFlow(src, sink)
else DefaultValueFlow::hasFlow(src, sink)
}
predicate hasTaintFlow(DataFlow::Node src, DataFlow::Node sink) {
if exists(EnableLegacyConfiguration e)
then getTaintFlowConfig().hasFlow(src, sink)
else DefaultTaintFlow::hasFlow(src, sink)
}
DataFlow::Configuration getValueFlowConfig() { result = any(DefaultValueFlowConf config) }
DataFlow::Configuration getTaintFlowConfig() { result = any(DefaultTaintFlowConf config) }

View File

@@ -1,7 +1,3 @@
import java
import semmle.code.java.dataflow.DataFlow
import TestUtilities.InlineFlowTest
class HasFlowTest extends InlineFlowTest {
override DataFlow::Configuration getTaintFlowConfig() { none() }
}

View File

@@ -42,31 +42,31 @@ public class Test {
public static void test1() {
Test t = new Test();
t.fluentNoop().fluentSet(source()).fluentNoop();
sink(t.get()); // $hasTaintFlow
sink(t.get()); // $hasValueFlow
}
public static void test2() {
Test t = new Test();
Test.identity(t).fluentNoop().fluentSet(source()).fluentNoop();
sink(t.get()); // $hasTaintFlow
sink(t.get()); // $hasValueFlow
}
public static void test3() {
Test t = new Test();
t.indirectlyFluentNoop().fluentSet(source()).fluentNoop();
sink(t.get()); // $hasTaintFlow
sink(t.get()); // $hasValueFlow
}
public static void testModel1() {
Test t = new Test();
t.indirectlyFluentNoop().modelledFluentMethod().fluentSet(source()).fluentNoop();
sink(t.get()); // $hasTaintFlow
sink(t.get()); // $hasValueFlow
}
public static void testModel2() {
Test t = new Test();
Test.modelledIdentity(t).indirectlyFluentNoop().modelledFluentMethod().fluentSet(source()).fluentNoop();
sink(t.get()); // $hasTaintFlow
sink(t.get()); // $hasValueFlow
}
}

View File

@@ -12,7 +12,3 @@ class IdentityModel extends ValuePreservingMethod {
override predicate returnsValue(int arg) { arg = 0 }
}
class HasFlowTest extends InlineFlowTest {
override DataFlow::Configuration getValueFlowConfig() { none() }
}

View File

@@ -14,54 +14,46 @@ predicate sink0(Node n) {
)
}
class Conf1 extends Configuration {
Conf1() { this = "inoutbarriers1" }
module Conf1 implements ConfigSig {
predicate isSource(Node n) { src0(n) }
override predicate isSource(Node n) { src0(n) }
override predicate isSink(Node n) { sink0(n) }
predicate isSink(Node n) { sink0(n) }
}
class Conf2 extends Configuration {
Conf2() { this = "inoutbarriers2" }
module Conf2 implements ConfigSig {
predicate isSource(Node n) { src0(n) }
override predicate isSource(Node n) { src0(n) }
predicate isSink(Node n) { sink0(n) }
override predicate isSink(Node n) { sink0(n) }
override predicate isBarrierIn(Node n) { src0(n) }
predicate isBarrierIn(Node n) { src0(n) }
}
class Conf3 extends Configuration {
Conf3() { this = "inoutbarriers3" }
module Conf3 implements ConfigSig {
predicate isSource(Node n) { src0(n) }
override predicate isSource(Node n) { src0(n) }
predicate isSink(Node n) { sink0(n) }
override predicate isSink(Node n) { sink0(n) }
override predicate isBarrierOut(Node n) { sink0(n) }
predicate isBarrierOut(Node n) { sink0(n) }
}
class Conf4 extends Configuration {
Conf4() { this = "inoutbarriers4" }
module Conf4 implements ConfigSig {
predicate isSource(Node n) { src0(n) }
override predicate isSource(Node n) { src0(n) }
predicate isSink(Node n) { sink0(n) }
override predicate isSink(Node n) { sink0(n) }
predicate isBarrierIn(Node n) { src0(n) }
override predicate isBarrierIn(Node n) { src0(n) }
override predicate isBarrierOut(Node n) { sink0(n) }
predicate isBarrierOut(Node n) { sink0(n) }
}
predicate flow(Node src, Node sink, string s) {
any(Conf1 c).hasFlow(src, sink) and s = "nobarrier"
Make<Conf1>::hasFlow(src, sink) and s = "nobarrier"
or
any(Conf2 c).hasFlow(src, sink) and s = "srcbarrier"
Make<Conf2>::hasFlow(src, sink) and s = "srcbarrier"
or
any(Conf3 c).hasFlow(src, sink) and s = "sinkbarrier"
Make<Conf3>::hasFlow(src, sink) and s = "sinkbarrier"
or
any(Conf4 c).hasFlow(src, sink) and s = "both"
Make<Conf4>::hasFlow(src, sink) and s = "both"
}
from Node src, Node sink, string s

View File

@@ -1,18 +1,19 @@
import java
import semmle.code.java.dataflow.DataFlow
import DataFlow
import PartialPathGraph
class Conf extends Configuration {
Conf() { this = "partial flow" }
module Config implements ConfigSig {
predicate isSource(Node n) { n.asExpr().(MethodAccess).getMethod().hasName("src") }
override predicate isSource(Node n) { n.asExpr().(MethodAccess).getMethod().hasName("src") }
override predicate isSink(Node n) { n.asExpr().(Argument).getCall().getCallee().hasName("sink") }
override int explorationLimit() { result = 10 }
predicate isSink(Node n) { n.asExpr().(Argument).getCall().getCallee().hasName("sink") }
}
from PartialPathNode n, int dist
where any(Conf c).hasPartialFlow(_, n, dist)
int explorationLimit() { result = 10 }
module PartialFlow = Make<Config>::FlowExploration<explorationLimit/0>;
import PartialFlow::PartialPathGraph
from PartialFlow::PartialPathNode n, int dist
where PartialFlow::hasPartialFlow(_, n, dist)
select dist, n

View File

@@ -1,18 +1,19 @@
import java
import semmle.code.java.dataflow.DataFlow
import DataFlow
import PartialPathGraph
class Conf extends Configuration {
Conf() { this = "partial flow" }
module Config implements ConfigSig {
predicate isSource(Node n) { n.asExpr().(MethodAccess).getMethod().hasName("src") }
override predicate isSource(Node n) { n.asExpr().(MethodAccess).getMethod().hasName("src") }
override predicate isSink(Node n) { n.asExpr().(Argument).getCall().getCallee().hasName("sink") }
override int explorationLimit() { result = 10 }
predicate isSink(Node n) { n.asExpr().(Argument).getCall().getCallee().hasName("sink") }
}
from PartialPathNode n, int dist
where any(Conf c).hasPartialFlowRev(n, _, dist)
int explorationLimit() { result = 10 }
module PartialFlow = Make<Config>::FlowExploration<explorationLimit/0>;
import PartialFlow::PartialPathGraph
from PartialFlow::PartialPathNode n, int dist
where PartialFlow::hasPartialFlowRev(n, _, dist)
select dist, n

View File

@@ -39,22 +39,26 @@ predicate step(Node n1, Node n2, string s1, string s2) {
predicate checkNode(Node n) { n.asExpr().(Argument).getCall().getCallee().hasName("check") }
class Conf extends TaintTracking::Configuration {
Conf() { this = "qltest:state" }
module Conf implements DataFlow::StateConfigSig {
class FlowState = string;
override predicate isSource(Node n, FlowState s) { src(n, s) }
predicate isSource(Node n, FlowState s) { src(n, s) }
override predicate isSink(Node n, FlowState s) { sink(n, s) }
predicate isSink(Node n, FlowState s) { sink(n, s) }
override predicate isSanitizer(Node n, FlowState s) { bar(n, s) }
predicate isBarrier(Node n, FlowState s) { bar(n, s) }
override predicate isAdditionalTaintStep(Node n1, FlowState s1, Node n2, FlowState s2) {
predicate isAdditionalFlowStep(Node n1, FlowState s1, Node n2, FlowState s2) {
step(n1, n2, s1, s2)
}
override int explorationLimit() { result = 0 }
}
int explorationLimit() { result = 0 }
module Flow = TaintTracking::MakeWithState<Conf>;
module PartialFlow = Flow::FlowExploration<explorationLimit/0>;
class HasFlowTest extends InlineExpectationsTest {
HasFlowTest() { this = "HasFlowTest" }
@@ -62,16 +66,16 @@ class HasFlowTest extends InlineExpectationsTest {
override predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "flow" and
exists(PathNode src, PathNode sink, Conf conf |
conf.hasFlowPath(src, sink) and
exists(Flow::PathNode src, Flow::PathNode sink |
Flow::hasFlowPath(src, sink) and
sink.getNode().getLocation() = location and
element = sink.toString() and
value = src.getState()
)
or
tag = "pFwd" and
exists(PartialPathNode src, PartialPathNode node, Conf conf |
conf.hasPartialFlow(src, node, _) and
exists(PartialFlow::PartialPathNode src, PartialFlow::PartialPathNode node |
PartialFlow::hasPartialFlow(src, node, _) and
checkNode(node.getNode()) and
node.getNode().getLocation() = location and
element = node.toString() and
@@ -79,8 +83,8 @@ class HasFlowTest extends InlineExpectationsTest {
)
or
tag = "pRev" and
exists(PartialPathNode node, PartialPathNode sink, Conf conf |
conf.hasPartialFlowRev(node, sink, _) and
exists(PartialFlow::PartialPathNode node, PartialFlow::PartialPathNode sink |
PartialFlow::hasPartialFlowRev(node, sink, _) and
checkNode(node.getNode()) and
node.getNode().getLocation() = location and
element = node.toString() and

View File

@@ -6,42 +6,44 @@ class A {
return "tainted";
}
public static void sink(Object o) { }
public static void test1() {
String bad = source(); // $ hasTaintFlow
String bad = source();
String good = "hi";
bad.formatted(good); // $ hasTaintFlow
good.formatted("a", bad, "b", good); // $ hasTaintFlow
String.format("%s%s", bad, good); // $ hasTaintFlow
String.format("%s", good);
String.format("%s %s %s %s %s %s %s %s %s %s ", "a", "a", "a", "a", "a", "a", "a", "a", "a", bad); // $ hasTaintFlow
sink(bad.formatted(good)); // $ hasTaintFlow
sink(good.formatted("a", bad, "b", good)); // $ hasTaintFlow
sink(String.format("%s%s", bad, good)); // $ hasTaintFlow
sink(String.format("%s", good));
sink(String.format("%s %s %s %s %s %s %s %s %s %s ", "a", "a", "a", "a", "a", "a", "a", "a", "a", bad)); // $ hasTaintFlow
}
public static void test2() {
String bad = source(); // $ hasTaintFlow
String bad = source();
Formatter f = new Formatter();
f.toString();
f.format("%s", bad); // $ hasTaintFlow
f.toString(); // $ hasTaintFlow
sink(f.toString());
sink(f.format("%s", bad)); // $ hasTaintFlow
sink(f.toString()); // $ hasTaintFlow
}
public static void test3() {
String bad = source(); // $ hasTaintFlow
String bad = source();
StringBuilder sb = new StringBuilder();
Formatter f = new Formatter(sb);
sb.toString(); // $ hasTaintFlow false positive
f.format("%s", bad); // $ hasTaintFlow
sb.toString(); // $ hasTaintFlow
sink(sb.toString()); // $ SPURIOUS: hasTaintFlow
sink(f.format("%s", bad)); // $ hasTaintFlow
sink(sb.toString()); // $ hasTaintFlow
}
public static void test4() {
String bad = source(); // $ hasTaintFlow
String bad = source();
StringBuilder sb = new StringBuilder();
sb.append(bad); // $ hasTaintFlow
sink(sb.append(bad)); // $ hasTaintFlow
new Formatter(sb).format("ok").toString(); // $ hasTaintFlow
sink(new Formatter(sb).format("ok").toString()); // $ hasTaintFlow
}
}
}

View File

@@ -1,8 +1,2 @@
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.TaintTracking
import TestUtilities.InlineFlowTest
class TaintFlowConf extends DefaultTaintFlowConf {
override predicate isSink(DataFlow::Node n) { n instanceof DataFlow::ExprNode }
}

View File

@@ -3,6 +3,10 @@ import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.FlowSources
import TestUtilities.InlineFlowTest
class EnableLegacy extends EnableLegacyConfiguration {
EnableLegacy() { exists(this) }
}
class TaintFlowConf extends DefaultTaintFlowConf {
override predicate isSource(DataFlow::Node n) {
super.isSource(n)

View File

@@ -2,6 +2,10 @@ import java
import semmle.code.java.dataflow.FlowSources
import TestUtilities.InlineFlowTest
class EnableLegacy extends EnableLegacyConfiguration {
EnableLegacy() { exists(this) }
}
class ProviderTaintFlowConf extends DefaultTaintFlowConf {
override predicate isSource(DataFlow::Node n) { n instanceof RemoteFlowSource }
}

View File

@@ -3,6 +3,10 @@ import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.FlowSources
import TestUtilities.InlineFlowTest
class EnableLegacy extends EnableLegacyConfiguration {
EnableLegacy() { exists(this) }
}
class Conf extends TaintTracking::Configuration {
Conf() { this = "test:AndroidExternalFlowConf" }

View File

@@ -2,6 +2,10 @@ import java
import TestUtilities.InlineFlowTest
import semmle.code.java.dataflow.FlowSources
class EnableLegacy extends EnableLegacyConfiguration {
EnableLegacy() { exists(this) }
}
class SliceValueFlowConf extends DefaultValueFlowConf {
override predicate isSource(DataFlow::Node source) {
super.isSource(source) or source instanceof RemoteFlowSource

View File

@@ -2,6 +2,10 @@ import java
import semmle.code.java.dataflow.FlowSources
import TestUtilities.InlineFlowTest
class EnableLegacy extends EnableLegacyConfiguration {
EnableLegacy() { exists(this) }
}
class SourceValueFlowConf extends DefaultValueFlowConf {
override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
}

View File

@@ -5,6 +5,10 @@ import semmle.code.java.security.XSS
import semmle.code.java.security.UrlRedirect
import TestUtilities.InlineFlowTest
class EnableLegacy extends EnableLegacyConfiguration {
EnableLegacy() { exists(this) }
}
class Conf extends TaintTracking::Configuration {
Conf() { this = "qltest:frameworks:apache-http" }

View File

@@ -1,6 +1,10 @@
import java
import TestUtilities.InlineFlowTest
class EnableLegacy extends EnableLegacyConfiguration {
EnableLegacy() { exists(this) }
}
class FlowConf extends DefaultValueFlowConf {
override predicate isSink(DataFlow::Node n) { super.isSink(n) or sinkNode(n, "open-url") }
}

View File

@@ -3,6 +3,10 @@ import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.FlowSources
import TestUtilities.InlineFlowTest
class EnableLegacy extends EnableLegacyConfiguration {
EnableLegacy() { exists(this) }
}
class Conf extends TaintTracking::Configuration {
Conf() { this = "qltest:frameworks:rabbitmq" }

View File

@@ -1,6 +1,10 @@
import java
import TestUtilities.InlineFlowTest
class EnableLegacy extends EnableLegacyConfiguration {
EnableLegacy() { exists(this) }
}
class FlowConf extends DefaultValueFlowConf {
override predicate isSink(DataFlow::Node n) { super.isSink(n) or sinkNode(n, "open-url") }
}

View File

@@ -2,6 +2,10 @@ import java
import semmle.code.java.dataflow.FlowSources
import TestUtilities.InlineFlowTest
class EnableLegacy extends EnableLegacyConfiguration {
EnableLegacy() { exists(this) }
}
class ValueFlowConf extends DataFlow::Configuration {
ValueFlowConf() { this = "ValueFlowConf" }

View File

@@ -2,6 +2,10 @@ import java
import semmle.code.java.security.PathSanitizer
import TestUtilities.InlineFlowTest
class EnableLegacy extends EnableLegacyConfiguration {
EnableLegacy() { exists(this) }
}
class PathSanitizerConf extends DefaultTaintFlowConf {
override predicate isSanitizer(DataFlow::Node sanitizer) {
sanitizer instanceof PathInjectionSanitizer

View File

@@ -2,6 +2,10 @@ import java
import TestUtilities.InlineFlowTest
import semmle.code.java.security.PartialPathTraversalQuery
class EnableLegacy extends EnableLegacyConfiguration {
EnableLegacy() { exists(this) }
}
class TestRemoteSource extends RemoteFlowSource {
TestRemoteSource() { this.asParameter().hasName(["dir", "path"]) }

View File

@@ -2,6 +2,10 @@ import java
import semmle.code.java.security.LogInjectionQuery
import TestUtilities.InlineFlowTest
class EnableLegacy extends EnableLegacyConfiguration {
EnableLegacy() { exists(this) }
}
private class TestSource extends RemoteFlowSource {
TestSource() { this.asExpr().(MethodAccess).getMethod().hasName("source") }

View File

@@ -2,6 +2,10 @@ import java
import TestUtilities.InlineFlowTest
import semmle.code.java.security.IntentUriPermissionManipulationQuery
class EnableLegacy extends EnableLegacyConfiguration {
EnableLegacy() { exists(this) }
}
class IntentUriPermissionManipulationTest extends InlineFlowTest {
override DataFlow::Configuration getValueFlowConfig() { none() }

View File

@@ -2,6 +2,10 @@ import java
import semmle.code.java.security.InsecureTrustManagerQuery
import TestUtilities.InlineFlowTest
class EnableLegacy extends EnableLegacyConfiguration {
EnableLegacy() { exists(this) }
}
class InsecureTrustManagerTest extends InlineFlowTest {
override DataFlow::Configuration getValueFlowConfig() {
result = any(InsecureTrustManagerConfiguration c)

View File

@@ -2,6 +2,10 @@ import java
import TestUtilities.InlineFlowTest
import semmle.code.java.security.UnsafeContentUriResolutionQuery
class EnableLegacy extends EnableLegacyConfiguration {
EnableLegacy() { exists(this) }
}
class Test extends InlineFlowTest {
override DataFlow::Configuration getValueFlowConfig() { none() }

View File

@@ -2,6 +2,10 @@ import java
import semmle.code.java.security.FragmentInjectionQuery
import TestUtilities.InlineFlowTest
class EnableLegacy extends EnableLegacyConfiguration {
EnableLegacy() { exists(this) }
}
class Test extends InlineFlowTest {
override DataFlow::Configuration getValueFlowConfig() { none() }

View File

@@ -2,6 +2,10 @@ import java
import TestUtilities.InlineFlowTest
import semmle.code.java.security.WebviewDubuggingEnabledQuery
class EnableLegacy extends EnableLegacyConfiguration {
EnableLegacy() { exists(this) }
}
class HasFlowTest extends InlineFlowTest {
override DataFlow::Configuration getTaintFlowConfig() { none() }

View File

@@ -2,6 +2,10 @@ import java
import TestUtilities.InlineFlowTest
import semmle.code.java.security.SensitiveLoggingQuery
class EnableLegacy extends EnableLegacyConfiguration {
EnableLegacy() { exists(this) }
}
class HasFlowTest extends InlineFlowTest {
override DataFlow::Configuration getTaintFlowConfig() {
result instanceof SensitiveLoggerConfiguration

View File

@@ -3,6 +3,10 @@ import TestUtilities.InlineExpectationsTest
import TestUtilities.InlineFlowTest
import semmle.code.java.security.RsaWithoutOaepQuery
class EnableLegacy extends EnableLegacyConfiguration {
EnableLegacy() { exists(this) }
}
class HasFlowTest extends InlineFlowTest {
override DataFlow::Configuration getTaintFlowConfig() { result instanceof RsaWithoutOaepConfig }

View File

@@ -3,6 +3,10 @@ import semmle.code.java.security.AndroidSensitiveCommunicationQuery
import TestUtilities.InlineExpectationsTest
import TestUtilities.InlineFlowTest
class EnableLegacy extends EnableLegacyConfiguration {
EnableLegacy() { exists(this) }
}
class HasFlowTest extends InlineFlowTest {
override DataFlow::Configuration getTaintFlowConfig() {
result = any(SensitiveCommunicationConfig c)