diff --git a/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/Flows.expected b/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/Flows.expected new file mode 100644 index 00000000000..81332464f79 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/Flows.expected @@ -0,0 +1,2 @@ +failures +invalidModelRow diff --git a/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/Flows.ql b/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/Flows.ql new file mode 100644 index 00000000000..7356e691328 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/Flows.ql @@ -0,0 +1,96 @@ +import go +import semmle.go.dataflow.ExternalFlow +import CsvValidation +// import DataFlow::PartialPathGraph +import TestUtilities.InlineExpectationsTest + +class SummaryModelTest extends DataFlow::FunctionModel { + FunctionInput inp; + FunctionOutput outp; + + SummaryModelTest() { + this.hasQualifiedName("github.com/nonexistent/test", "FunctionWithParameter") and + (inp.isParameter(0) and outp.isResult()) + or + // Cannot model this correctly for data flow, but it should work for taint flow + this.hasQualifiedName("github.com/nonexistent/test", "FunctionWithSliceParameter") and + (inp.isParameter(0) and outp.isResult()) + or + this.hasQualifiedName("github.com/nonexistent/test", "FunctionWithVarArgsParameter") and + (inp.isParameter(_) and outp.isResult()) + // or + // this.hasQualifiedName("github.com/nonexistent/test", "FunctionWithSliceOfStructsParameter")) and + // (inp.isParameter(0) and outp.isResult) + // or + // this.hasQualifiedName("github.com/nonexistent/test", "FunctionWithVarArgsOfStructsParameter")) and + // (inp.isParameter(0) and outp.isResult) + } + + override predicate hasDataFlow(FunctionInput input, FunctionOutput output) { + input = inp and output = outp + } +} + +class DataConfiguration extends DataFlow::Configuration { + DataConfiguration() { this = "data-configuration" } + + override predicate isSource(DataFlow::Node source) { + source = any(DataFlow::CallNode c | c.getCalleeName() = "source").getResult(0) + } + + override predicate isSink(DataFlow::Node sink) { + sink = any(DataFlow::CallNode c | c.getCalleeName() = "sink").getArgument(0) + } + + override int explorationLimit() { result = 10 } // this is different! +} + +class DataFlowTest extends InlineExpectationsTest { + DataFlowTest() { this = "DataFlowTest" } + + override string getARelevantTag() { result = "dataflow" } + + override predicate hasActualResult(string file, int line, string element, string tag, string value) { + tag = "dataflow" and + exists(DataFlow::Node sink | any(DataConfiguration c).hasFlow(_, sink) | + element = sink.toString() and + value = "" and + sink.hasLocationInfo(file, line, _, _, _) + ) + } +} + +class TaintConfiguration extends TaintTracking::Configuration { + TaintConfiguration() { this = "taint-configuration" } + + override predicate isSource(DataFlow::Node source) { + source = any(DataFlow::CallNode c | c.getCalleeName() = "source").getResult(0) + } + + override predicate isSink(DataFlow::Node sink) { + sink = any(DataFlow::CallNode c | c.getCalleeName() = "sink").getArgument(0) + } + + override int explorationLimit() { result = 10 } // this is different! +} + +class TaintFlowTest extends InlineExpectationsTest { + TaintFlowTest() { this = "TaintFlowTest" } + + override string getARelevantTag() { result = "taintflow" } + + override predicate hasActualResult(string file, int line, string element, string tag, string value) { + tag = "taintflow" and + exists(DataFlow::Node sink | any(TaintConfiguration c).hasFlow(_, sink) | + element = sink.toString() and + value = "" and + sink.hasLocationInfo(file, line, _, _, _) + ) + } +} +// from TaintConfiguration cfg, DataFlow::PartialPathNode source, DataFlow::PartialPathNode sink +// where +// cfg.hasPartialFlow(source, sink, _) +// and +// source.getNode().hasLocationInfo(_, 22, _, _, _) +// select sink, source, sink, "Partial flow from unsanitized user data" diff --git a/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/go.mod b/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/go.mod new file mode 100644 index 00000000000..ed18764ed28 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/go.mod @@ -0,0 +1,5 @@ +module semmle.go.Packages + +go 1.17 + +require github.com/nonexistent/test v0.0.0-20200203000000-0000000000000 diff --git a/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/main.go b/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/main.go new file mode 100644 index 00000000000..b9c163b695d --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "github.com/nonexistent/test" +) + +func source() string { + return "untrusted data" +} + +func sink(string) { +} + +func main() { + s := source() + sink(test.FunctionWithParameter(s)) // $ taintflow dataflow + + stringSlice := []string{source()} + sink(stringSlice[0]) // $ taintflow dataflow + + s0 := "" + s1 := source() + sSlice := []string{s0, s1} + sink(test.FunctionWithParameter(sSlice[1])) // $ taintflow dataflow + sink(test.FunctionWithSliceParameter(sSlice)) // $ taintflow MISSING: dataflow + sink(test.FunctionWithVarArgsParameter(sSlice...)) // $ taintflow MISSING: dataflow + sink(test.FunctionWithVarArgsParameter(s0, s1)) // $ MISSING: taintflow dataflow + + sliceOfStructs := []test.A{{Field: source()}} + sink(sliceOfStructs[0].Field) // $ taintflow dataflow + + a0 := test.A{Field: ""} + a1 := test.A{Field: source()} + aSlice := []test.A{a0, a1} + sink(test.FunctionWithSliceOfStructsParameter(aSlice)) // $ MISSING: taintflow dataflow + sink(test.FunctionWithVarArgsOfStructsParameter(aSlice...)) // $ MISSING: taintflow dataflow + sink(test.FunctionWithVarArgsOfStructsParameter(a0, a1)) // $ MISSING: taintflow dataflow +} diff --git a/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/semmle.go.Packages b/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/semmle.go.Packages new file mode 100755 index 00000000000..e3880ac8d5d Binary files /dev/null and b/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/semmle.go.Packages differ diff --git a/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/vendor/github.com/nonexistent/test/stub.go b/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/vendor/github.com/nonexistent/test/stub.go new file mode 100644 index 00000000000..66f3da7d659 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/vendor/github.com/nonexistent/test/stub.go @@ -0,0 +1,25 @@ +package test + +type A struct { + Field string +} + +func FunctionWithParameter(s string) string { + return "" +} + +func FunctionWithSliceParameter(s []string) string { + return "" +} + +func FunctionWithVarArgsParameter(s ...string) string { + return "" +} + +func FunctionWithSliceOfStructsParameter(s []A) string { + return "" +} + +func FunctionWithVarArgsOfStructsParameter(s ...A) string { + return "" +} diff --git a/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/vendor/modules.txt b/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/vendor/modules.txt new file mode 100644 index 00000000000..b62dbf8819b --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/vendor/modules.txt @@ -0,0 +1,3 @@ +# github.com/nonexistent/test v0.0.0-20200203000000-0000000000000 +## explicit +github.com/nonexistent/test