Ruby: Rewrite InlineFlowTest as a parameterized module

This commit is contained in:
Jeroen Ketema
2023-06-14 18:07:57 +02:00
parent 742eb8dd12
commit d82c3ce11a
40 changed files with 188 additions and 140 deletions

View File

@@ -4,10 +4,11 @@
* Example for a test.ql:
* ```ql
* import TestUtilities.InlineFlowTest
* import DefaultFlowTest
* import PathGraph
*
* from DataFlow::PathNode source, DataFlow::PathNode sink, DefaultValueFlowConf conf
* where conf.hasFlowPath(source, sink)
* from ValueFlow::PathNode source, ValueFlow::PathNode sink
* where ValueFlow::flowPath(source, sink)
* select sink, source, sink, "$@", source, source.toString()
* ```
*
@@ -20,14 +21,10 @@
* sink(t); // $ hasTaintFlow=2
* ```
*
* If you're not interested in a specific flow type, you can disable either value or taint flow expectations as follows:
* ```ql
* class HasFlowTest extends InlineFlowTest {
* override DataFlow::Configuration getTaintFlowConfig() { none() }
*
* override DataFlow::Configuration getValueFlowConfig() { none() }
* }
* ```
* If you are only interested in value flow, then instead of importing `DefaultFlowTest`, you can import
* `ValueFlowTest<DefaultFlowConfig>`. Similarly, if you are only interested in taint flow, then instead of
* importing `DefaultFlowTest`, you can import `TaintFlowTest<DefaultFlowConfig>`. In both cases
* `DefaultFlowConfig` can be replaced by another implementation of `DataFlow::ConfigSig`.
*
* If you need more fine-grained tuning, consider implementing a test using `InlineExpectationsTest`.
*/
@@ -38,72 +35,62 @@ import codeql.ruby.TaintTracking
import TestUtilities.InlineExpectationsTest
import TestUtilities.InlineFlowTestUtil
class DefaultValueFlowConf extends DataFlow::Configuration {
DefaultValueFlowConf() { this = "qltest:defaultValueFlowConf" }
module DefaultFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { defaultSource(source) }
override predicate isSource(DataFlow::Node n) { defaultSource(n) }
predicate isSink(DataFlow::Node sink) { defaultSink(sink) }
override predicate isSink(DataFlow::Node n) { defaultSink(n) }
override int fieldFlowBranchLimit() { result = 1000 }
int fieldFlowBranchLimit() { result = 1000 }
}
class DefaultTaintFlowConf extends TaintTracking::Configuration {
DefaultTaintFlowConf() { this = "qltest:defaultTaintFlowConf" }
private module NoFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { none() }
override predicate isSource(DataFlow::Node n) { defaultSource(n) }
override predicate isSink(DataFlow::Node n) { defaultSink(n) }
override int fieldFlowBranchLimit() { result = 1000 }
predicate isSink(DataFlow::Node sink) { none() }
}
class InlineFlowTest extends InlineExpectationsTest {
InlineFlowTest() { this = "HasFlowTest" }
module FlowTest<DataFlow::ConfigSig ValueFlowConfig, DataFlow::ConfigSig TaintFlowConfig> {
module ValueFlow = DataFlow::Global<ValueFlowConfig>;
override string getARelevantTag() { result = ["hasValueFlow", "hasTaintFlow"] }
module TaintFlow = TaintTracking::Global<TaintFlowConfig>;
override predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "hasValueFlow" and
exists(DataFlow::Node src, DataFlow::Node sink | this.getValueFlowConfig().hasFlow(src, sink) |
sink.getLocation() = location and
element = sink.toString() and
if exists(getSourceArgString(src)) then value = getSourceArgString(src) else value = ""
)
or
tag = "hasTaintFlow" and
exists(DataFlow::Node src, DataFlow::Node sink |
this.getTaintFlowConfig().hasFlow(src, sink) and
not this.getValueFlowConfig().hasFlow(src, sink)
|
sink.getLocation() = location and
element = sink.toString() and
if exists(getSourceArgString(src)) then value = getSourceArgString(src) else value = ""
)
}
private module InlineTest implements TestSig {
string getARelevantTag() { result = ["hasValueFlow", "hasTaintFlow"] }
DataFlow::Configuration getValueFlowConfig() { result = any(DefaultValueFlowConf config) }
DataFlow::Configuration getTaintFlowConfig() { result = any(DefaultTaintFlowConf config) }
}
module PathGraph {
private import DataFlow::PathGraph as PG
private class PathNode extends DataFlow::PathNode {
PathNode() {
this.getConfiguration() =
[any(InlineFlowTest t).getValueFlowConfig(), any(InlineFlowTest t).getTaintFlowConfig()]
predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "hasValueFlow" and
exists(DataFlow::Node src, DataFlow::Node sink | ValueFlow::flow(src, sink) |
sink.getLocation() = location and
element = sink.toString() and
if exists(getSourceArgString(src)) then value = getSourceArgString(src) else value = ""
)
or
tag = "hasTaintFlow" and
exists(DataFlow::Node src, DataFlow::Node sink |
TaintFlow::flow(src, sink) and not ValueFlow::flow(src, sink)
|
sink.getLocation() = location and
element = sink.toString() and
if exists(getSourceArgString(src)) then value = getSourceArgString(src) else value = ""
)
}
}
/** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
query predicate edges(PathNode a, PathNode b) { PG::edges(a, b) }
import MakeTest<InlineTest>
import DataFlow::MergePathGraph<ValueFlow::PathNode, TaintFlow::PathNode, ValueFlow::PathGraph, TaintFlow::PathGraph>
/** Holds if `n` is a node in the graph of data flow path explanations. */
query predicate nodes(PathNode n, string key, string val) { PG::nodes(n, key, val) }
query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) {
PG::subpaths(arg, par, ret, out)
predicate flowPath(PathNode source, PathNode sink) {
ValueFlow::flowPath(source.asPathNode1(), sink.asPathNode1()) or
TaintFlow::flowPath(source.asPathNode2(), sink.asPathNode2())
}
}
module DefaultFlowTest = FlowTest<DefaultFlowConfig, DefaultFlowConfig>;
module ValueFlowTest<DataFlow::ConfigSig ValueFlowConfig> {
import FlowTest<ValueFlowConfig, NoFlowConfig>
}
module TaintFlowTest<DataFlow::ConfigSig TaintFlowConfig> {
import FlowTest<NoFlowConfig, TaintFlowConfig>
}