Merge pull request #9867 from michaelnebel/csharp/nosummary

C#: Negative summaries (ie. no flow through)
This commit is contained in:
Michael Nebel
2022-08-24 12:06:05 +02:00
committed by GitHub
53 changed files with 72376 additions and 632 deletions

View File

@@ -5,12 +5,13 @@
* *
* The CSV specification has the following columns: * The CSV specification has the following columns:
* - Sources: * - Sources:
* `namespace; type; subtypes; name; signature; ext; output; kind` * `namespace; type; subtypes; name; signature; ext; output; kind; provenance`
* - Sinks: * - Sinks:
* `namespace; type; subtypes; name; signature; ext; input; kind` * `namespace; type; subtypes; name; signature; ext; input; kind; provenance`
* - Summaries: * - Summaries:
* `namespace; type; subtypes; name; signature; ext; input; output; kind` * `namespace; type; subtypes; name; signature; ext; input; output; kind; provenance`
* * - Negative Summaries:
* `namespace; type; name; signature; provenance`
* The interpretation of a row is similar to API-graphs with a left-to-right * The interpretation of a row is similar to API-graphs with a left-to-right
* reading. * reading.
* 1. The `namespace` column selects a namespace. * 1. The `namespace` column selects a namespace.
@@ -163,11 +164,27 @@ class SummaryModelCsv extends Unit {
abstract predicate row(string row); abstract predicate row(string row);
} }
private predicate sourceModel(string row) { any(SourceModelCsv s).row(row) } /**
* A unit class for adding negative summary model rows.
*
* Extend this class to add additional flow summary definitions.
*/
class NegativeSummaryModelCsv extends Unit {
/** Holds if `row` specifies a negative summary definition. */
abstract predicate row(string row);
}
private predicate sinkModel(string row) { any(SinkModelCsv s).row(row) } /** Holds if `row` is a source model. */
predicate sourceModel(string row) { any(SourceModelCsv s).row(row) }
private predicate summaryModel(string row) { any(SummaryModelCsv s).row(row) } /** Holds if `row` is a sink model. */
predicate sinkModel(string row) { any(SinkModelCsv s).row(row) }
/** Holds if `row` is a summary model. */
predicate summaryModel(string row) { any(SummaryModelCsv s).row(row) }
/** Holds if `row` is a negative summary model. */
predicate negativeSummaryModel(string row) { any(NegativeSummaryModelCsv s).row(row) }
/** Holds if a source model exists for the given parameters. */ /** Holds if a source model exists for the given parameters. */
predicate sourceModel( predicate sourceModel(
@@ -230,6 +247,20 @@ predicate summaryModel(
) )
} }
/** Holds if a summary model exists indicating there is no flow for the given parameters. */
predicate negativeSummaryModel(
string namespace, string type, string name, string signature, string provenance
) {
exists(string row |
negativeSummaryModel(row) and
row.splitAt(";", 0) = namespace and
row.splitAt(";", 1) = type and
row.splitAt(";", 2) = name and
row.splitAt(";", 3) = signature and
row.splitAt(";", 4) = provenance
)
}
private predicate relevantNamespace(string namespace) { private predicate relevantNamespace(string namespace) {
sourceModel(namespace, _, _, _, _, _, _, _, _) or sourceModel(namespace, _, _, _, _, _, _, _, _) or
sinkModel(namespace, _, _, _, _, _, _, _, _) or sinkModel(namespace, _, _, _, _, _, _, _, _) or
@@ -286,38 +317,7 @@ predicate modelCoverage(string namespace, int namespaces, string kind, string pa
/** Provides a query predicate to check the CSV data for validation errors. */ /** Provides a query predicate to check the CSV data for validation errors. */
module CsvValidation { module CsvValidation {
/** Holds if some row in a CSV-based flow model appears to contain typos. */ private string getInvalidModelInput() {
query predicate invalidModelRow(string msg) {
exists(
string pred, string namespace, string type, string name, string signature, string ext,
string provenance
|
sourceModel(namespace, type, _, name, signature, ext, _, _, provenance) and pred = "source"
or
sinkModel(namespace, type, _, name, signature, ext, _, _, provenance) and pred = "sink"
or
summaryModel(namespace, type, _, name, signature, ext, _, _, _, provenance) and
pred = "summary"
|
not namespace.regexpMatch("[a-zA-Z0-9_\\.]+") and
msg = "Dubious namespace \"" + namespace + "\" in " + pred + " model."
or
not type.regexpMatch("[a-zA-Z0-9_<>,\\+]+") and
msg = "Dubious type \"" + type + "\" in " + pred + " model."
or
not name.regexpMatch("[a-zA-Z0-9_<>,]*") and
msg = "Dubious member name \"" + name + "\" in " + pred + " model."
or
not signature.regexpMatch("|\\([a-zA-Z0-9_<>\\.\\+\\*,\\[\\]]*\\)") and
msg = "Dubious signature \"" + signature + "\" in " + pred + " model."
or
not ext.regexpMatch("|Attribute") and
msg = "Unrecognized extra API graph element \"" + ext + "\" in " + pred + " model."
or
not provenance = ["manual", "generated"] and
msg = "Unrecognized provenance description \"" + provenance + "\" in " + pred + " model."
)
or
exists(string pred, AccessPath input, string part | exists(string pred, AccessPath input, string part |
sinkModel(_, _, _, _, _, _, input, _, _) and pred = "sink" sinkModel(_, _, _, _, _, _, input, _, _) and pred = "sink"
or or
@@ -332,9 +332,11 @@ module CsvValidation {
part = input.getToken(_) and part = input.getToken(_) and
parseParam(part, _) parseParam(part, _)
) and ) and
msg = "Unrecognized input specification \"" + part + "\" in " + pred + " model." result = "Unrecognized input specification \"" + part + "\" in " + pred + " model."
) )
or }
private string getInvalidModelOutput() {
exists(string pred, string output, string part | exists(string pred, string output, string part |
sourceModel(_, _, _, _, _, _, output, _, _) and pred = "source" sourceModel(_, _, _, _, _, _, output, _, _) and pred = "source"
or or
@@ -343,58 +345,123 @@ module CsvValidation {
invalidSpecComponent(output, part) and invalidSpecComponent(output, part) and
not part = "" and not part = "" and
not (part = ["Argument", "Parameter"] and pred = "source") and not (part = ["Argument", "Parameter"] and pred = "source") and
msg = "Unrecognized output specification \"" + part + "\" in " + pred + " model." result = "Unrecognized output specification \"" + part + "\" in " + pred + " model."
) )
or }
exists(string pred, string row, int expect |
sourceModel(row) and expect = 9 and pred = "source" private string getInvalidModelKind() {
or
sinkModel(row) and expect = 9 and pred = "sink"
or
summaryModel(row) and expect = 10 and pred = "summary"
|
exists(int cols |
cols = 1 + max(int n | exists(row.splitAt(";", n))) and
cols != expect and
msg =
"Wrong number of columns in " + pred + " model row, expected " + expect + ", got " + cols +
" in " + row + "."
)
or
exists(string b |
b = row.splitAt(";", 2) and
not b = ["true", "false"] and
msg = "Invalid boolean \"" + b + "\" in " + pred + " model."
)
)
or
exists(string row, string kind | summaryModel(row) | exists(string row, string kind | summaryModel(row) |
kind = row.splitAt(";", 8) and kind = row.splitAt(";", 8) and
not kind = ["taint", "value"] and not kind = ["taint", "value"] and
msg = "Invalid kind \"" + kind + "\" in summary model." result = "Invalid kind \"" + kind + "\" in summary model."
) )
or or
exists(string row, string kind | sinkModel(row) | exists(string row, string kind | sinkModel(row) |
kind = row.splitAt(";", 7) and kind = row.splitAt(";", 7) and
not kind = ["code", "sql", "xss", "remote", "html"] and not kind = ["code", "sql", "xss", "remote", "html"] and
not kind.matches("encryption-%") and not kind.matches("encryption-%") and
msg = "Invalid kind \"" + kind + "\" in sink model." result = "Invalid kind \"" + kind + "\" in sink model."
) )
or or
exists(string row, string kind | sourceModel(row) | exists(string row, string kind | sourceModel(row) |
kind = row.splitAt(";", 7) and kind = row.splitAt(";", 7) and
not kind = ["local", "file"] and not kind = ["local", "file"] and
msg = "Invalid kind \"" + kind + "\" in source model." result = "Invalid kind \"" + kind + "\" in source model."
) )
} }
private string getInvalidModelSubtype() {
exists(string pred, string row |
sourceModel(row) and pred = "source"
or
sinkModel(row) and pred = "sink"
or
summaryModel(row) and pred = "summary"
|
exists(string b |
b = row.splitAt(";", 2) and
not b = ["true", "false"] and
result = "Invalid boolean \"" + b + "\" in " + pred + " model."
)
)
}
private string getInvalidModelColumnCount() {
exists(string pred, string row, int expect |
sourceModel(row) and expect = 9 and pred = "source"
or
sinkModel(row) and expect = 9 and pred = "sink"
or
summaryModel(row) and expect = 10 and pred = "summary"
or
negativeSummaryModel(row) and expect = 5 and pred = "negative summary"
|
exists(int cols |
cols = 1 + max(int n | exists(row.splitAt(";", n))) and
cols != expect and
result =
"Wrong number of columns in " + pred + " model row, expected " + expect + ", got " + cols +
" in " + row + "."
)
)
}
private string getInvalidModelSignature() {
exists(
string pred, string namespace, string type, string name, string signature, string ext,
string provenance
|
sourceModel(namespace, type, _, name, signature, ext, _, _, provenance) and pred = "source"
or
sinkModel(namespace, type, _, name, signature, ext, _, _, provenance) and pred = "sink"
or
summaryModel(namespace, type, _, name, signature, ext, _, _, _, provenance) and
pred = "summary"
or
negativeSummaryModel(namespace, type, name, signature, provenance) and
ext = "" and
pred = "negative summary"
|
not namespace.regexpMatch("[a-zA-Z0-9_\\.]+") and
result = "Dubious namespace \"" + namespace + "\" in " + pred + " model."
or
not type.regexpMatch("[a-zA-Z0-9_<>,\\+]+") and
result = "Dubious type \"" + type + "\" in " + pred + " model."
or
not name.regexpMatch("[a-zA-Z0-9_<>,]*") and
result = "Dubious member name \"" + name + "\" in " + pred + " model."
or
not signature.regexpMatch("|\\([a-zA-Z0-9_<>\\.\\+\\*,\\[\\]]*\\)") and
result = "Dubious signature \"" + signature + "\" in " + pred + " model."
or
not ext.regexpMatch("|Attribute") and
result = "Unrecognized extra API graph element \"" + ext + "\" in " + pred + " model."
or
not provenance = ["manual", "generated"] and
result = "Unrecognized provenance description \"" + provenance + "\" in " + pred + " model."
)
}
/** Holds if some row in a CSV-based flow model appears to contain typos. */
query predicate invalidModelRow(string msg) {
msg =
[
getInvalidModelSignature(), getInvalidModelInput(), getInvalidModelOutput(),
getInvalidModelSubtype(), getInvalidModelColumnCount(), getInvalidModelKind()
]
}
} }
private predicate elementSpec( private predicate elementSpec(
string namespace, string type, boolean subtypes, string name, string signature, string ext string namespace, string type, boolean subtypes, string name, string signature, string ext
) { ) {
sourceModel(namespace, type, subtypes, name, signature, ext, _, _, _) or sourceModel(namespace, type, subtypes, name, signature, ext, _, _, _)
sinkModel(namespace, type, subtypes, name, signature, ext, _, _, _) or or
sinkModel(namespace, type, subtypes, name, signature, ext, _, _, _)
or
summaryModel(namespace, type, subtypes, name, signature, ext, _, _, _, _) summaryModel(namespace, type, subtypes, name, signature, ext, _, _, _, _)
or
negativeSummaryModel(namespace, type, name, signature, _) and ext = "" and subtypes = false
} }
private predicate elementSpec( private predicate elementSpec(
@@ -508,7 +575,7 @@ private Element interpretElement0(
) )
} }
/** Gets the source/sink/summary element corresponding to the supplied parameters. */ /** Gets the source/sink/summary/negativesummary element corresponding to the supplied parameters. */
Element interpretElement( Element interpretElement(
string namespace, string type, boolean subtypes, string name, string signature, string ext string namespace, string type, boolean subtypes, string name, string signature, string ext
) { ) {

View File

@@ -173,7 +173,7 @@ module Ssa {
} }
/** /**
* Holds is this SSA definition is live at the end of basic block `bb`. * Holds if this SSA definition is live at the end of basic block `bb`.
* That is, this definition reaches the end of basic block `bb`, at which * That is, this definition reaches the end of basic block `bb`, at which
* point it is still live, without crossing another SSA definition of the * point it is still live, without crossing another SSA definition of the
* same source variable. * same source variable.

View File

@@ -2129,18 +2129,37 @@ module Csv {
if isBaseCallableOrPrototype(c) then result = "true" else result = "false" if isBaseCallableOrPrototype(c) then result = "true" else result = "false"
} }
/** Computes the first 6 columns for CSV rows of `c`. */ private predicate partialModel(
DotNet::Callable c, string namespace, string type, string name, string parameters
) {
c.getDeclaringType().hasQualifiedName(namespace, type) and
c.hasQualifiedName(_, name) and
parameters = "(" + parameterQualifiedTypeNamesToString(c) + ")"
}
/** Computes the first 6 columns for positive CSV rows of `c`. */
string asPartialModel(DotNet::Callable c) { string asPartialModel(DotNet::Callable c) {
exists(string namespace, string type, string name | exists(string namespace, string type, string name, string parameters |
c.getDeclaringType().hasQualifiedName(namespace, type) and partialModel(c, namespace, type, name, parameters) and
c.hasQualifiedName(_, name) and
result = result =
namespace + ";" // namespace + ";" //
+ type + ";" // + type + ";" //
+ getCallableOverride(c) + ";" // + getCallableOverride(c) + ";" //
+ name + ";" // + name + ";" //
+ "(" + parameterQualifiedTypeNamesToString(c) + ")" + ";" // + parameters + ";" //
+ /* ext + */ ";" // + /* ext + */ ";" //
) )
} }
/** Computes the first 4 columns for negative CSV rows of `c`. */
string asPartialNegativeModel(DotNet::Callable c) {
exists(string namespace, string type, string name, string parameters |
partialModel(c, namespace, type, name, parameters) and
result =
namespace + ";" //
+ type + ";" //
+ name + ";" //
+ parameters + ";" //
)
}
} }

View File

@@ -240,6 +240,16 @@ module Public {
*/ */
predicate isAutoGenerated() { none() } predicate isAutoGenerated() { none() }
} }
/** A callable with a flow summary stating there is no flow via the callable. */
class NegativeSummarizedCallable extends SummarizedCallableBase {
NegativeSummarizedCallable() { negativeSummaryElement(this, _) }
/**
* Holds if the negative summary is auto generated.
*/
predicate isAutoGenerated() { negativeSummaryElement(this, true) }
}
} }
/** /**
@@ -1094,7 +1104,7 @@ module Private {
/** Provides a query predicate for outputting a set of relevant flow summaries. */ /** Provides a query predicate for outputting a set of relevant flow summaries. */
module TestOutput { module TestOutput {
/** A flow summary to include in the `summary/3` query predicate. */ /** A flow summary to include in the `summary/1` query predicate. */
abstract class RelevantSummarizedCallable instanceof SummarizedCallable { abstract class RelevantSummarizedCallable instanceof SummarizedCallable {
/** Gets the string representation of this callable used by `summary/1`. */ /** Gets the string representation of this callable used by `summary/1`. */
abstract string getCallableCsv(); abstract string getCallableCsv();
@@ -1109,6 +1119,14 @@ module Private {
string toString() { result = super.toString() } string toString() { result = super.toString() }
} }
/** A flow summary to include in the `negativeSummary/1` query predicate. */
abstract class RelevantNegativeSummarizedCallable instanceof NegativeSummarizedCallable {
/** Gets the string representation of this callable used by `summary/1`. */
abstract string getCallableCsv();
string toString() { result = super.toString() }
}
/** Render the kind in the format used in flow summaries. */ /** Render the kind in the format used in flow summaries. */
private string renderKind(boolean preservesValue) { private string renderKind(boolean preservesValue) {
preservesValue = true and result = "value" preservesValue = true and result = "value"
@@ -1116,8 +1134,12 @@ module Private {
preservesValue = false and result = "taint" preservesValue = false and result = "taint"
} }
private string renderProvenance(RelevantSummarizedCallable c) { private string renderProvenance(SummarizedCallable c) {
if c.(SummarizedCallable).isAutoGenerated() then result = "generated" else result = "manual" if c.isAutoGenerated() then result = "generated" else result = "manual"
}
private string renderProvenanceNegative(NegativeSummarizedCallable c) {
if c.isAutoGenerated() then result = "generated" else result = "manual"
} }
/** /**
@@ -1132,8 +1154,23 @@ module Private {
| |
c.relevantSummary(input, output, preservesValue) and c.relevantSummary(input, output, preservesValue) and
csv = csv =
c.getCallableCsv() + getComponentStackCsv(input) + ";" + getComponentStackCsv(output) + c.getCallableCsv() // Callable information
";" + renderKind(preservesValue) + ";" + renderProvenance(c) + getComponentStackCsv(input) + ";" // input
+ getComponentStackCsv(output) + ";" // output
+ renderKind(preservesValue) + ";" // kind
+ renderProvenance(c) // provenance
)
}
/**
* Holds if a negative flow summary `csv` exists (semi-colon separated format). Used for testing purposes.
* The syntax is: "namespace;type;name;signature;provenance"",
*/
query predicate negativeSummary(string csv) {
exists(RelevantNegativeSummarizedCallable c |
csv =
c.getCallableCsv() // Callable information
+ renderProvenanceNegative(c) // provenance
) )
} }
} }

View File

@@ -114,6 +114,18 @@ predicate summaryElement(Callable c, string input, string output, string kind, b
) )
} }
/**
* Holds if a negative flow summary exists for `c`, which means that there is no
* flow through `c`. The flag `generated` states whether the summary is autogenerated.
*/
predicate negativeSummaryElement(Callable c, boolean generated) {
exists(string namespace, string type, string name, string signature, string provenance |
negativeSummaryModel(namespace, type, name, signature, provenance) and
generated = isGenerated(provenance) and
c = interpretElement(namespace, type, false, name, signature, "")
)
}
/** /**
* Holds if an external source specification exists for `e` with output specification * Holds if an external source specification exists for `e` with output specification
* `output`, kind `kind`, and a flag `generated` stating whether the source specification is * `output`, kind `kind`, and a flag `generated` stating whether the source specification is

View File

@@ -0,0 +1,9 @@
/** Provides modules for importing negative summaries. */
/**
* A module importing the frameworks that provide external flow data,
* ensuring that they are visible to the taint tracking / data flow library.
*/
private module Frameworks {
private import semmle.code.csharp.frameworks.GeneratedNegative
}

View File

@@ -0,0 +1,9 @@
/**
* A module importing all generated negative Models as Data models.
*/
import csharp
private module GeneratedFrameworks {
private import generated.dotnet.NegativeRuntime
}

File diff suppressed because it is too large Load Diff

View File

@@ -8,11 +8,14 @@
private import csharp private import csharp
private import semmle.code.csharp.dispatch.Dispatch private import semmle.code.csharp.dispatch.Dispatch
private import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
private import semmle.code.csharp.dataflow.internal.NegativeSummary
private import ExternalApi private import ExternalApi
private predicate getRelevantUsages(ExternalApi api, int usages) { private predicate getRelevantUsages(ExternalApi api, int usages) {
not api.isUninteresting() and not api.isUninteresting() and
not api.isSupported() and not api.isSupported() and
not api instanceof FlowSummaryImpl::Public::NegativeSummarizedCallable and
usages = strictcount(DispatchCall c | c = api.getACall()) usages = strictcount(DispatchCall c | c = api.getACall())
} }

View File

@@ -6,7 +6,7 @@
private import semmle.code.csharp.dataflow.ExternalFlow private import semmle.code.csharp.dataflow.ExternalFlow
private import internal.CaptureModels private import internal.CaptureModels
private import internal.CaptureFlow private import internal.CaptureSummaryFlow
from TargetApi api, string flow from TargetApi api, string flow
where flow = captureFlow(api) and hasSummary(api, false) where flow = captureFlow(api) and hasSummary(api, false)

View File

@@ -0,0 +1,15 @@
/**
* @name Capture negative summary models.
* @description Finds negative summary models to be used by other queries.
* @kind diagnostic
* @id cs/utils/model-generator/negative-summary-models
* @tags model-generator
*/
private import semmle.code.csharp.dataflow.ExternalFlow
private import internal.CaptureModels
private import internal.CaptureSummaryFlow
from TargetApi api, string noflow
where noflow = captureNoFlow(api) and not hasSummary(api, false)
select noflow order by noflow

View File

@@ -8,7 +8,7 @@
private import semmle.code.csharp.dataflow.ExternalFlow private import semmle.code.csharp.dataflow.ExternalFlow
private import internal.CaptureModels private import internal.CaptureModels
private import internal.CaptureFlow private import internal.CaptureSummaryFlow
from TargetApi api, string flow from TargetApi api, string flow
where flow = captureFlow(api) and not hasSummary(api, false) where flow = captureFlow(api) and not hasSummary(api, false)

View File

@@ -48,6 +48,8 @@ private string asSummaryModel(TargetApi api, string input, string output, string
+ "generated" + "generated"
} }
string asNegativeSummaryModel(TargetApi api) { result = asPartialNegativeModel(api) + "generated" }
/** /**
* Gets the value summary model for `api` with `input` and `output`. * Gets the value summary model for `api` with `input` and `output`.
*/ */

View File

@@ -36,7 +36,8 @@ private predicate isRelevantForModels(CS::Callable api) {
api.getDeclaringType().getNamespace().getQualifiedName() != "" and api.getDeclaringType().getNamespace().getQualifiedName() != "" and
not api instanceof CS::ConversionOperator and not api instanceof CS::ConversionOperator and
not api instanceof Util::MainMethod and not api instanceof Util::MainMethod and
not isHigherOrder(api) not isHigherOrder(api) and
not api instanceof CS::Destructor
} }
/** /**
@@ -55,6 +56,8 @@ class TargetApiSpecific extends DotNet::Callable {
predicate asPartialModel = DataFlowPrivate::Csv::asPartialModel/1; predicate asPartialModel = DataFlowPrivate::Csv::asPartialModel/1;
predicate asPartialNegativeModel = DataFlowPrivate::Csv::asPartialNegativeModel/1;
/** /**
* Holds for type `t` for fields that are relevant as an intermediate * Holds for type `t` for fields that are relevant as an intermediate
* read or write step in the data flow analysis. * read or write step in the data flow analysis.

View File

@@ -79,3 +79,12 @@ string captureFlow(TargetApi api) {
result = captureQualifierFlow(api) or result = captureQualifierFlow(api) or
result = captureThroughFlow(api) result = captureThroughFlow(api)
} }
/**
* Gets the negative summary for `api`, if any.
* A negative summary is generated, if there does not exist any positive flow.
*/
string captureNoFlow(TargetApi api) {
not exists(captureFlow(api)) and
result = asNegativeSummaryModel(api)
}

View File

@@ -3,8 +3,8 @@
*/ */
import csharp import csharp
import semmle.code.csharp.dataflow.ExternalFlow
import DataFlow::PathGraph import DataFlow::PathGraph
import semmle.code.csharp.dataflow.ExternalFlow
import CsvValidation import CsvValidation
class SummaryModelTest extends SummaryModelCsv { class SummaryModelTest extends SummaryModelCsv {

View File

@@ -0,0 +1,8 @@
/**
* CSV Validation of negative summaries.
*/
import csharp
import semmle.code.csharp.dataflow.ExternalFlow
import CsvValidation
import semmle.code.csharp.dataflow.internal.NegativeSummary

View File

@@ -1,8 +1,8 @@
import csharp import csharp
import DataFlow import DataFlow
import semmle.code.csharp.dataflow.ExternalFlow import semmle.code.csharp.dataflow.ExternalFlow
import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
import CsvValidation import CsvValidation
import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
class SinkModelTest extends SinkModelCsv { class SinkModelTest extends SinkModelCsv {
override predicate row(string row) { override predicate row(string row) {

View File

@@ -1,8 +1,8 @@
import csharp import csharp
import DataFlow import DataFlow
import semmle.code.csharp.dataflow.ExternalFlow import semmle.code.csharp.dataflow.ExternalFlow
import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
import CsvValidation import CsvValidation
import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
class SourceModelTest extends SourceModelCsv { class SourceModelTest extends SourceModelCsv {
override predicate row(string row) { override predicate row(string row) {

View File

@@ -1,10 +1,10 @@
import csharp import csharp
import DataFlow import DataFlow
import semmle.code.csharp.dataflow.ExternalFlow import semmle.code.csharp.dataflow.ExternalFlow
import CsvValidation
import semmle.code.csharp.dataflow.FlowSummary import semmle.code.csharp.dataflow.FlowSummary
import semmle.code.csharp.dataflow.internal.DataFlowDispatch as DataFlowDispatch import semmle.code.csharp.dataflow.internal.DataFlowDispatch as DataFlowDispatch
import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
import CsvValidation
private class SummaryModelTest extends SummaryModelCsv { private class SummaryModelTest extends SummaryModelCsv {
override predicate row(string row) { override predicate row(string row) {

View File

@@ -1,5 +1,16 @@
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate
private import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
import shared.FlowSummaries import shared.FlowSummaries
private class IncludeAllSummarizedCallable extends IncludeSummarizedCallable { private class IncludeAllSummarizedCallable extends IncludeSummarizedCallable {
IncludeAllSummarizedCallable() { exists(this) } IncludeAllSummarizedCallable() { exists(this) }
} }
private class IncludeNegativeSummarizedCallable extends RelevantNegativeSummarizedCallable {
IncludeNegativeSummarizedCallable() {
this instanceof FlowSummaryImpl::Public::NegativeSummarizedCallable
}
/** Gets a string representing the callable in semi-colon separated format for use in flow summaries. */
final override string getCallableCsv() { result = Csv::asPartialNegativeModel(this) }
}

View File

@@ -1,3 +1,4 @@
summary
| Microsoft.CSharp.RuntimeBinder;Binder;false;BinaryOperation;(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags,System.Linq.Expressions.ExpressionType,System.Type,System.Collections.Generic.IEnumerable<Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>);;Argument[2];ReturnValue;taint;generated | | Microsoft.CSharp.RuntimeBinder;Binder;false;BinaryOperation;(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags,System.Linq.Expressions.ExpressionType,System.Type,System.Collections.Generic.IEnumerable<Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>);;Argument[2];ReturnValue;taint;generated |
| Microsoft.CSharp.RuntimeBinder;Binder;false;BinaryOperation;(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags,System.Linq.Expressions.ExpressionType,System.Type,System.Collections.Generic.IEnumerable<Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>);;Argument[3].Element;ReturnValue;taint;generated | | Microsoft.CSharp.RuntimeBinder;Binder;false;BinaryOperation;(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags,System.Linq.Expressions.ExpressionType,System.Type,System.Collections.Generic.IEnumerable<Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>);;Argument[3].Element;ReturnValue;taint;generated |
| Microsoft.CSharp.RuntimeBinder;Binder;false;Convert;(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags,System.Type,System.Type);;Argument[2];ReturnValue;taint;generated | | Microsoft.CSharp.RuntimeBinder;Binder;false;Convert;(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags,System.Type,System.Type);;Argument[2];ReturnValue;taint;generated |
@@ -10022,3 +10023,4 @@
| System;ValueTuple<>;false;ToString;();;Argument[this];ReturnValue;taint;generated | | System;ValueTuple<>;false;ToString;();;Argument[this];ReturnValue;taint;generated |
| System;ValueTuple<>;false;ValueTuple;(T1);;Argument[0];Argument[this].Field[System.ValueTuple<>.Item1];value;manual | | System;ValueTuple<>;false;ValueTuple;(T1);;Argument[0];Argument[this].Field[System.ValueTuple<>.Item1];value;manual |
| System;ValueTuple<>;false;get_Item;(System.Int32);;Argument[this].Field[System.ValueTuple<>.Item1];ReturnValue;value;manual | | System;ValueTuple<>;false;get_Item;(System.Int32);;Argument[this].Field[System.ValueTuple<>.Item1];ReturnValue;value;manual |
negativeSummary

View File

@@ -131,6 +131,7 @@ summary
| System.Data.Entity;DbSet<>;false;AttachRange;(System.Collections.Generic.IEnumerable<T>);;Argument[0].Element;Argument[this].Element;value;manual | | System.Data.Entity;DbSet<>;false;AttachRange;(System.Collections.Generic.IEnumerable<T>);;Argument[0].Element;Argument[this].Element;value;manual |
| System.Data.Entity;DbSet<>;false;Update;(T);;Argument[0];Argument[this].Element;value;manual | | System.Data.Entity;DbSet<>;false;Update;(T);;Argument[0];Argument[this].Element;value;manual |
| System.Data.Entity;DbSet<>;false;UpdateRange;(System.Collections.Generic.IEnumerable<T>);;Argument[0].Element;Argument[this].Element;value;manual | | System.Data.Entity;DbSet<>;false;UpdateRange;(System.Collections.Generic.IEnumerable<T>);;Argument[0].Element;Argument[this].Element;value;manual |
negativeSummary
sourceNode sourceNode
sinkNode sinkNode
| EntityFrameworkCore.cs:72:36:72:40 | "sql" | sql | | EntityFrameworkCore.cs:72:36:72:40 | "sql" | sql |

View File

@@ -1,4 +0,0 @@
| System.Private.CoreLib.dll#System#DateTime.AddYears(System.Int32) | 2 |
| System.Private.CoreLib.dll#System#DateTime.AddDays(System.Double) | 1 |
| System.Private.CoreLib.dll#System#DateTime.DateTime(System.Int32,System.Int32,System.Int32) | 1 |
| System.Private.CoreLib.dll#System#Guid.Parse(System.String) | 1 |

View File

@@ -1,6 +1,7 @@
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate
import semmle.code.csharp.dataflow.FlowSummary import semmle.code.csharp.dataflow.FlowSummary
import semmle.code.csharp.dataflow.internal.FlowSummaryImpl::Private::TestOutput import semmle.code.csharp.dataflow.internal.FlowSummaryImpl::Private::TestOutput
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate import semmle.code.csharp.dataflow.internal.NegativeSummary
abstract class IncludeSummarizedCallable extends RelevantSummarizedCallable { abstract class IncludeSummarizedCallable extends RelevantSummarizedCallable {
IncludeSummarizedCallable() { IncludeSummarizedCallable() {

View File

@@ -0,0 +1,25 @@
| NoSummaries;BaseClass;M1;(System.String);generated |
| NoSummaries;BaseClass;M2;(System.String);generated |
| NoSummaries;EquatableBound;Equals;(System.Object);generated |
| NoSummaries;EquatableUnBound<>;Equals;(T);generated |
| NoSummaries;SimpleTypes;M1;(System.Boolean);generated |
| NoSummaries;SimpleTypes;M2;(System.Boolean);generated |
| NoSummaries;SimpleTypes;M3;(System.Int32);generated |
| NoSummaries;SimpleTypes;M4;(System.Int32);generated |
| Sinks;NewSinks;WrapFieldResponseWriteFile;();generated |
| Sinks;NewSinks;WrapPrivateFieldResponseWriteFile;();generated |
| Sinks;NewSinks;WrapPrivatePropResponseWriteFile;();generated |
| Sinks;NewSinks;WrapPropPrivateSetResponseWriteFile;();generated |
| Sinks;NewSinks;WrapPropResponseWriteFile;();generated |
| Sinks;NewSinks;WrapResponseWrite;(System.Object);generated |
| Sinks;NewSinks;WrapResponseWriteFile;(System.String);generated |
| Sinks;NewSinks;get_PrivateSetTaintedProp;();generated |
| Sinks;NewSinks;get_TaintedProp;();generated |
| Sinks;NewSinks;set_PrivateSetTaintedProp;(System.String);generated |
| Sinks;NewSinks;set_TaintedProp;(System.String);generated |
| Sources;NewSources;WrapConsoleReadKey;();generated |
| Sources;NewSources;WrapConsoleReadLine;();generated |
| Sources;NewSources;WrapConsoleReadLineAndProcees;(System.String);generated |
| Summaries;EqualsGetHashCodeNoFlow;Equals;(System.Object);generated |
| Summaries;EqualsGetHashCodeNoFlow;GetHashCode;();generated |
| Summaries;OperatorFlow;op_Increment;(Summaries.OperatorFlow);generated |

View File

@@ -0,0 +1 @@
utils/model-generator/CaptureNegativeSummaryModels.ql

View File

@@ -99,3 +99,15 @@ public class HigherOrderParameters
return map(o); return map(o);
} }
} }
public abstract class BaseClass
{
// Negative summary.
public virtual string M1(string s)
{
return "";
}
// Negative summary.
public abstract string M2(string s);
}

View File

@@ -6,6 +6,9 @@ namespace Summaries;
public class BasicFlow public class BasicFlow
{ {
// No flow summary and no negative summary either.
~BasicFlow() { }
private string tainted; private string tainted;
public BasicFlow ReturnThis(object input) public BasicFlow ReturnThis(object input)

View File

@@ -85,12 +85,6 @@ private class BuiltinModel extends SummaryModelCsv {
} }
} }
private predicate sourceModelCsv(string row) { none() }
private predicate sinkModelCsv(string row) { none() }
private predicate summaryModelCsv(string row) { none() }
/** /**
* A unit class for adding additional source model rows. * A unit class for adding additional source model rows.
* *
@@ -121,20 +115,14 @@ class SummaryModelCsv extends Unit {
abstract predicate row(string row); abstract predicate row(string row);
} }
private predicate sourceModel(string row) { /** Holds if `row` is a source model. */
sourceModelCsv(row) or predicate sourceModel(string row) { any(SourceModelCsv s).row(row) }
any(SourceModelCsv s).row(row)
}
private predicate sinkModel(string row) { /** Holds if `row` is a sink model. */
sinkModelCsv(row) or predicate sinkModel(string row) { any(SinkModelCsv s).row(row) }
any(SinkModelCsv s).row(row)
}
private predicate summaryModel(string row) { /** Holds if `row` is a summary model. */
summaryModelCsv(row) or predicate summaryModel(string row) { any(SummaryModelCsv s).row(row) }
any(SummaryModelCsv s).row(row)
}
/** Holds if a source model exists for the given parameters. */ /** Holds if a source model exists for the given parameters. */
predicate sourceModel( predicate sourceModel(
@@ -271,31 +259,7 @@ predicate modelCoverage(string package, int pkgs, string kind, string part, int
/** Provides a query predicate to check the CSV data for validation errors. */ /** Provides a query predicate to check the CSV data for validation errors. */
module CsvValidation { module CsvValidation {
/** Holds if some row in a CSV-based flow model appears to contain typos. */ private string getInvalidModelInput() {
query predicate invalidModelRow(string msg) {
exists(string pred, string namespace, string type, string name, string signature, string ext |
sourceModel(namespace, type, _, name, signature, ext, _, _) and pred = "source"
or
sinkModel(namespace, type, _, name, signature, ext, _, _) and pred = "sink"
or
summaryModel(namespace, type, _, name, signature, ext, _, _, _) and pred = "summary"
|
not namespace.regexpMatch("[a-zA-Z0-9_\\./]*") and
msg = "Dubious namespace \"" + namespace + "\" in " + pred + " model."
or
not type.regexpMatch("[a-zA-Z0-9_\\$<>]*") and
msg = "Dubious type \"" + type + "\" in " + pred + " model."
or
not name.regexpMatch("[a-zA-Z0-9_]*") and
msg = "Dubious name \"" + name + "\" in " + pred + " model."
or
not signature.regexpMatch("|\\([a-zA-Z0-9_\\.\\$<>,\\[\\]]*\\)") and
msg = "Dubious signature \"" + signature + "\" in " + pred + " model."
or
not ext.regexpMatch("|Annotated") and
msg = "Unrecognized extra API graph element \"" + ext + "\" in " + pred + " model."
)
or
exists(string pred, AccessPath input, string part | exists(string pred, AccessPath input, string part |
sinkModel(_, _, _, _, _, _, input, _) and pred = "sink" sinkModel(_, _, _, _, _, _, input, _) and pred = "sink"
or or
@@ -309,9 +273,11 @@ module CsvValidation {
part = input.getToken(_) and part = input.getToken(_) and
parseParam(part, _) parseParam(part, _)
) and ) and
msg = "Unrecognized input specification \"" + part + "\" in " + pred + " model." result = "Unrecognized input specification \"" + part + "\" in " + pred + " model."
) )
or }
private string getInvalidModelOutput() {
exists(string pred, string output, string part | exists(string pred, string output, string part |
sourceModel(_, _, _, _, _, _, output, _) and pred = "source" sourceModel(_, _, _, _, _, _, output, _) and pred = "source"
or or
@@ -320,9 +286,35 @@ module CsvValidation {
invalidSpecComponent(output, part) and invalidSpecComponent(output, part) and
not part = "" and not part = "" and
not (part = "Parameter" and pred = "source") and not (part = "Parameter" and pred = "source") and
msg = "Unrecognized output specification \"" + part + "\" in " + pred + " model." result = "Unrecognized output specification \"" + part + "\" in " + pred + " model."
) )
or }
private string getInvalidModelKind() {
exists(string row, string kind | summaryModel(row) |
kind = row.splitAt(";", 8) and
not kind = ["taint", "value"] and
result = "Invalid kind \"" + kind + "\" in summary model."
)
}
private string getInvalidModelSubtype() {
exists(string pred, string row |
sourceModel(row) and pred = "source"
or
sinkModel(row) and pred = "sink"
or
summaryModel(row) and pred = "summary"
|
exists(string b |
b = row.splitAt(";", 2) and
not b = ["true", "false"] and
result = "Invalid boolean \"" + b + "\" in " + pred + " model."
)
)
}
private string getInvalidModelColumnCount() {
exists(string pred, string row, int expect | exists(string pred, string row, int expect |
sourceModel(row) and expect = 8 and pred = "source" sourceModel(row) and expect = 8 and pred = "source"
or or
@@ -333,18 +325,46 @@ module CsvValidation {
exists(int cols | exists(int cols |
cols = 1 + max(int n | exists(row.splitAt(";", n))) and cols = 1 + max(int n | exists(row.splitAt(";", n))) and
cols != expect and cols != expect and
msg = result =
"Wrong number of columns in " + pred + " model row, expected " + expect + ", got " + cols + "Wrong number of columns in " + pred + " model row, expected " + expect + ", got " + cols +
"." "."
) )
or
exists(string b |
b = row.splitAt(";", 2) and
not b = ["true", "false"] and
msg = "Invalid boolean \"" + b + "\" in " + pred + " model."
)
) )
} }
private string getInvalidModelSignature() {
exists(string pred, string namespace, string type, string name, string signature, string ext |
sourceModel(namespace, type, _, name, signature, ext, _, _) and pred = "source"
or
sinkModel(namespace, type, _, name, signature, ext, _, _) and pred = "sink"
or
summaryModel(namespace, type, _, name, signature, ext, _, _, _) and pred = "summary"
|
not namespace.regexpMatch("[a-zA-Z0-9_\\./]*") and
result = "Dubious namespace \"" + namespace + "\" in " + pred + " model."
or
not type.regexpMatch("[a-zA-Z0-9_\\$<>]*") and
result = "Dubious type \"" + type + "\" in " + pred + " model."
or
not name.regexpMatch("[a-zA-Z0-9_]*") and
result = "Dubious name \"" + name + "\" in " + pred + " model."
or
not signature.regexpMatch("|\\([a-zA-Z0-9_\\.\\$<>,\\[\\]]*\\)") and
result = "Dubious signature \"" + signature + "\" in " + pred + " model."
or
not ext.regexpMatch("|Annotated") and
result = "Unrecognized extra API graph element \"" + ext + "\" in " + pred + " model."
)
}
/** Holds if some row in a CSV-based flow model appears to contain typos. */
query predicate invalidModelRow(string msg) {
msg =
[
getInvalidModelSignature(), getInvalidModelInput(), getInvalidModelOutput(),
getInvalidModelSubtype(), getInvalidModelColumnCount(), getInvalidModelKind()
]
}
} }
pragma[nomagic] pragma[nomagic]

View File

@@ -4,8 +4,8 @@
import go import go
import semmle.go.dataflow.ExternalFlow import semmle.go.dataflow.ExternalFlow
import semmle.go.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
import CsvValidation import CsvValidation
import semmle.go.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
import TestUtilities.InlineFlowTest import TestUtilities.InlineFlowTest
class SummaryModelTest extends SummaryModelCsv { class SummaryModelTest extends SummaryModelCsv {

View File

@@ -1,7 +1,7 @@
import go import go
import semmle.go.dataflow.ExternalFlow import semmle.go.dataflow.ExternalFlow
import semmle.go.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
import CsvValidation import CsvValidation
import semmle.go.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
class SummaryModelTest extends SummaryModelCsv { class SummaryModelTest extends SummaryModelCsv {
override predicate row(string row) { override predicate row(string row) {

View File

@@ -5,11 +5,13 @@
* *
* The CSV specification has the following columns: * The CSV specification has the following columns:
* - Sources: * - Sources:
* `namespace; type; subtypes; name; signature; ext; output; kind` * `namespace; type; subtypes; name; signature; ext; output; kind; provenance`
* - Sinks: * - Sinks:
* `namespace; type; subtypes; name; signature; ext; input; kind` * `namespace; type; subtypes; name; signature; ext; input; kind; provenance`
* - Summaries: * - Summaries:
* `namespace; type; subtypes; name; signature; ext; input; output; kind` * `namespace; type; subtypes; name; signature; ext; input; output; kind; provenance`
* - Negative Summaries:
* `namespace; type; name; signature; provenance`
* *
* The interpretation of a row is similar to API-graphs with a left-to-right * The interpretation of a row is similar to API-graphs with a left-to-right
* reading. * reading.
@@ -152,234 +154,6 @@ private module Frameworks {
private import semmle.code.java.frameworks.KotlinStdLib private import semmle.code.java.frameworks.KotlinStdLib
} }
private predicate sourceModelCsv(string row) {
row =
[
// org.springframework.security.web.savedrequest.SavedRequest
"org.springframework.security.web.savedrequest;SavedRequest;true;getRedirectUrl;;;ReturnValue;remote;manual",
"org.springframework.security.web.savedrequest;SavedRequest;true;getCookies;;;ReturnValue;remote;manual",
"org.springframework.security.web.savedrequest;SavedRequest;true;getHeaderValues;;;ReturnValue;remote;manual",
"org.springframework.security.web.savedrequest;SavedRequest;true;getHeaderNames;;;ReturnValue;remote;manual",
"org.springframework.security.web.savedrequest;SavedRequest;true;getParameterValues;;;ReturnValue;remote;manual",
"org.springframework.security.web.savedrequest;SavedRequest;true;getParameterMap;;;ReturnValue;remote;manual",
// ServletRequestGetParameterMethod
"javax.servlet;ServletRequest;false;getParameter;(String);;ReturnValue;remote;manual",
"javax.servlet;ServletRequest;false;getParameterValues;(String);;ReturnValue;remote;manual",
"javax.servlet.http;HttpServletRequest;false;getParameter;(String);;ReturnValue;remote;manual",
"javax.servlet.http;HttpServletRequest;false;getParameterValues;(String);;ReturnValue;remote;manual",
// ServletRequestGetParameterMapMethod
"javax.servlet;ServletRequest;false;getParameterMap;();;ReturnValue;remote;manual",
"javax.servlet.http;HttpServletRequest;false;getParameterMap;();;ReturnValue;remote;manual",
// ServletRequestGetParameterNamesMethod
"javax.servlet;ServletRequest;false;getParameterNames;();;ReturnValue;remote;manual",
"javax.servlet.http;HttpServletRequest;false;getParameterNames;();;ReturnValue;remote;manual",
// HttpServletRequestGetQueryStringMethod
"javax.servlet.http;HttpServletRequest;false;getQueryString;();;ReturnValue;remote;manual",
//
// URLConnectionGetInputStreamMethod
"java.net;URLConnection;false;getInputStream;();;ReturnValue;remote;manual",
// SocketGetInputStreamMethod
"java.net;Socket;false;getInputStream;();;ReturnValue;remote;manual",
// BeanValidationSource
"javax.validation;ConstraintValidator;true;isValid;;;Parameter[0];remote;manual",
// SpringMultipartRequestSource
"org.springframework.web.multipart;MultipartRequest;true;getFile;(String);;ReturnValue;remote;manual",
"org.springframework.web.multipart;MultipartRequest;true;getFileMap;();;ReturnValue;remote;manual",
"org.springframework.web.multipart;MultipartRequest;true;getFileNames;();;ReturnValue;remote;manual",
"org.springframework.web.multipart;MultipartRequest;true;getFiles;(String);;ReturnValue;remote;manual",
"org.springframework.web.multipart;MultipartRequest;true;getMultiFileMap;();;ReturnValue;remote;manual",
"org.springframework.web.multipart;MultipartRequest;true;getMultipartContentType;(String);;ReturnValue;remote;manual",
// SpringMultipartFileSource
"org.springframework.web.multipart;MultipartFile;true;getBytes;();;ReturnValue;remote;manual",
"org.springframework.web.multipart;MultipartFile;true;getContentType;();;ReturnValue;remote;manual",
"org.springframework.web.multipart;MultipartFile;true;getInputStream;();;ReturnValue;remote;manual",
"org.springframework.web.multipart;MultipartFile;true;getName;();;ReturnValue;remote;manual",
"org.springframework.web.multipart;MultipartFile;true;getOriginalFilename;();;ReturnValue;remote;manual",
"org.springframework.web.multipart;MultipartFile;true;getResource;();;ReturnValue;remote;manual",
// HttpServletRequest.get*
"javax.servlet.http;HttpServletRequest;false;getHeader;(String);;ReturnValue;remote;manual",
"javax.servlet.http;HttpServletRequest;false;getHeaders;(String);;ReturnValue;remote;manual",
"javax.servlet.http;HttpServletRequest;false;getHeaderNames;();;ReturnValue;remote;manual",
"javax.servlet.http;HttpServletRequest;false;getPathInfo;();;ReturnValue;remote;manual",
"javax.servlet.http;HttpServletRequest;false;getRequestURI;();;ReturnValue;remote;manual",
"javax.servlet.http;HttpServletRequest;false;getRequestURL;();;ReturnValue;remote;manual",
"javax.servlet.http;HttpServletRequest;false;getRemoteUser;();;ReturnValue;remote;manual",
// SpringWebRequestGetMethod
"org.springframework.web.context.request;WebRequest;false;getDescription;;;ReturnValue;remote;manual",
"org.springframework.web.context.request;WebRequest;false;getHeader;;;ReturnValue;remote;manual",
"org.springframework.web.context.request;WebRequest;false;getHeaderNames;;;ReturnValue;remote;manual",
"org.springframework.web.context.request;WebRequest;false;getHeaderValues;;;ReturnValue;remote;manual",
"org.springframework.web.context.request;WebRequest;false;getParameter;;;ReturnValue;remote;manual",
"org.springframework.web.context.request;WebRequest;false;getParameterMap;;;ReturnValue;remote;manual",
"org.springframework.web.context.request;WebRequest;false;getParameterNames;;;ReturnValue;remote;manual",
"org.springframework.web.context.request;WebRequest;false;getParameterValues;;;ReturnValue;remote;manual",
// TODO consider org.springframework.web.context.request.WebRequest.getRemoteUser
// ServletRequestGetBodyMethod
"javax.servlet;ServletRequest;false;getInputStream;();;ReturnValue;remote;manual",
"javax.servlet;ServletRequest;false;getReader;();;ReturnValue;remote;manual",
// CookieGet*
"javax.servlet.http;Cookie;false;getValue;();;ReturnValue;remote;manual",
"javax.servlet.http;Cookie;false;getName;();;ReturnValue;remote;manual",
"javax.servlet.http;Cookie;false;getComment;();;ReturnValue;remote;manual",
// ApacheHttp*
"org.apache.http;HttpMessage;false;getParams;();;ReturnValue;remote;manual",
"org.apache.http;HttpEntity;false;getContent;();;ReturnValue;remote;manual",
// In the setting of Android we assume that XML has been transmitted over
// the network, so may be tainted.
// XmlPullGetMethod
"org.xmlpull.v1;XmlPullParser;false;getName;();;ReturnValue;remote;manual",
"org.xmlpull.v1;XmlPullParser;false;getNamespace;();;ReturnValue;remote;manual",
"org.xmlpull.v1;XmlPullParser;false;getText;();;ReturnValue;remote;manual",
// XmlAttrSetGetMethod
"android.util;AttributeSet;false;getAttributeBooleanValue;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getAttributeCount;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getAttributeFloatValue;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getAttributeIntValue;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getAttributeListValue;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getAttributeName;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getAttributeNameResource;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getAttributeNamespace;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getAttributeResourceValue;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getAttributeUnsignedIntValue;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getAttributeValue;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getClassAttribute;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getIdAttribute;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getIdAttributeResourceValue;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getPositionDescription;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getStyleAttribute;;;ReturnValue;remote;manual",
// The current URL in a browser may be untrusted or uncontrolled.
// WebViewGetUrlMethod
"android.webkit;WebView;false;getUrl;();;ReturnValue;remote;manual",
"android.webkit;WebView;false;getOriginalUrl;();;ReturnValue;remote;manual",
// SpringRestTemplateResponseEntityMethod
"org.springframework.web.client;RestTemplate;false;exchange;;;ReturnValue;remote;manual",
"org.springframework.web.client;RestTemplate;false;getForEntity;;;ReturnValue;remote;manual",
"org.springframework.web.client;RestTemplate;false;postForEntity;;;ReturnValue;remote;manual",
// WebSocketMessageParameterSource
"java.net.http;WebSocket$Listener;true;onText;(WebSocket,CharSequence,boolean);;Parameter[1];remote;manual",
// PlayRequestGetMethod
"play.mvc;Http$RequestHeader;false;queryString;;;ReturnValue;remote;manual",
"play.mvc;Http$RequestHeader;false;getQueryString;;;ReturnValue;remote;manual",
"play.mvc;Http$RequestHeader;false;header;;;ReturnValue;remote;manual",
"play.mvc;Http$RequestHeader;false;getHeader;;;ReturnValue;remote;manual"
]
}
private predicate sinkModelCsv(string row) {
row =
[
// Open URL
"java.net;URL;false;openConnection;;;Argument[-1];open-url;manual",
"java.net;URL;false;openStream;;;Argument[-1];open-url;manual",
"java.net.http;HttpRequest;false;newBuilder;;;Argument[0];open-url;manual",
"java.net.http;HttpRequest$Builder;false;uri;;;Argument[0];open-url;manual",
"java.net;URLClassLoader;false;URLClassLoader;(URL[]);;Argument[0];open-url;manual",
"java.net;URLClassLoader;false;URLClassLoader;(URL[],ClassLoader);;Argument[0];open-url;manual",
"java.net;URLClassLoader;false;URLClassLoader;(URL[],ClassLoader,URLStreamHandlerFactory);;Argument[0];open-url;manual",
"java.net;URLClassLoader;false;URLClassLoader;(String,URL[],ClassLoader);;Argument[1];open-url;manual",
"java.net;URLClassLoader;false;URLClassLoader;(String,URL[],ClassLoader,URLStreamHandlerFactory);;Argument[1];open-url;manual",
"java.net;URLClassLoader;false;newInstance;;;Argument[0];open-url;manual",
// Bean validation
"javax.validation;ConstraintValidatorContext;true;buildConstraintViolationWithTemplate;;;Argument[0];bean-validation;manual",
// Set hostname
"javax.net.ssl;HttpsURLConnection;true;setDefaultHostnameVerifier;;;Argument[0];set-hostname-verifier;manual",
"javax.net.ssl;HttpsURLConnection;true;setHostnameVerifier;;;Argument[0];set-hostname-verifier;manual"
]
}
private predicate summaryModelCsv(string row) {
row =
[
// qualifier to arg
"java.io;InputStream;true;read;(byte[]);;Argument[-1];Argument[0];taint;manual",
"java.io;InputStream;true;read;(byte[],int,int);;Argument[-1];Argument[0];taint;manual",
"java.io;InputStream;true;readNBytes;(byte[],int,int);;Argument[-1];Argument[0];taint;manual",
"java.io;InputStream;true;transferTo;(OutputStream);;Argument[-1];Argument[0];taint;manual",
"java.io;ByteArrayOutputStream;false;writeTo;;;Argument[-1];Argument[0];taint;manual",
"java.io;Reader;true;read;;;Argument[-1];Argument[0];taint;manual",
// qualifier to return
"java.io;ByteArrayOutputStream;false;toByteArray;;;Argument[-1];ReturnValue;taint;manual",
"java.io;ByteArrayOutputStream;false;toString;;;Argument[-1];ReturnValue;taint;manual",
"java.io;InputStream;true;readAllBytes;;;Argument[-1];ReturnValue;taint;manual",
"java.io;InputStream;true;readNBytes;(int);;Argument[-1];ReturnValue;taint;manual",
"java.util;StringTokenizer;false;nextElement;();;Argument[-1];ReturnValue;taint;manual",
"java.util;StringTokenizer;false;nextToken;;;Argument[-1];ReturnValue;taint;manual",
"javax.xml.transform.sax;SAXSource;false;getInputSource;;;Argument[-1];ReturnValue;taint;manual",
"javax.xml.transform.stream;StreamSource;false;getInputStream;;;Argument[-1];ReturnValue;taint;manual",
"java.nio;ByteBuffer;false;get;;;Argument[-1];ReturnValue;taint;manual",
"java.net;URI;false;toURL;;;Argument[-1];ReturnValue;taint;manual",
"java.net;URI;false;toString;;;Argument[-1];ReturnValue;taint;manual",
"java.net;URI;false;toAsciiString;;;Argument[-1];ReturnValue;taint;manual",
"java.io;File;true;toURI;;;Argument[-1];ReturnValue;taint;manual",
"java.io;File;true;toPath;;;Argument[-1];ReturnValue;taint;manual",
"java.io;File;true;getAbsoluteFile;;;Argument[-1];ReturnValue;taint;manual",
"java.io;File;true;getCanonicalFile;;;Argument[-1];ReturnValue;taint;manual",
"java.io;File;true;getAbsolutePath;;;Argument[-1];ReturnValue;taint;manual",
"java.io;File;true;getCanonicalPath;;;Argument[-1];ReturnValue;taint;manual",
"java.nio;ByteBuffer;false;array;();;Argument[-1];ReturnValue;taint;manual",
"java.nio.file;Path;true;normalize;;;Argument[-1];ReturnValue;taint;manual",
"java.nio.file;Path;true;resolve;;;Argument[-1..0];ReturnValue;taint;manual",
"java.nio.file;Path;false;toFile;;;Argument[-1];ReturnValue;taint;manual",
"java.nio.file;Path;true;toString;;;Argument[-1];ReturnValue;taint;manual",
"java.nio.file;Path;true;toUri;;;Argument[-1];ReturnValue;taint;manual",
"java.nio.file;Paths;true;get;;;Argument[0..1];ReturnValue;taint;manual",
"java.io;BufferedReader;true;readLine;;;Argument[-1];ReturnValue;taint;manual",
"java.io;Reader;true;read;();;Argument[-1];ReturnValue;taint;manual",
// arg to return
"java.nio;ByteBuffer;false;wrap;(byte[]);;Argument[0];ReturnValue;taint;manual",
"java.util;Base64$Encoder;false;encode;(byte[]);;Argument[0];ReturnValue;taint;manual",
"java.util;Base64$Encoder;false;encode;(ByteBuffer);;Argument[0];ReturnValue;taint;manual",
"java.util;Base64$Encoder;false;encodeToString;(byte[]);;Argument[0];ReturnValue;taint;manual",
"java.util;Base64$Encoder;false;wrap;(OutputStream);;Argument[0];ReturnValue;taint;manual",
"java.util;Base64$Decoder;false;decode;(byte[]);;Argument[0];ReturnValue;taint;manual",
"java.util;Base64$Decoder;false;decode;(ByteBuffer);;Argument[0];ReturnValue;taint;manual",
"java.util;Base64$Decoder;false;decode;(String);;Argument[0];ReturnValue;taint;manual",
"java.util;Base64$Decoder;false;wrap;(InputStream);;Argument[0];ReturnValue;taint;manual",
"cn.hutool.core.codec;Base64;true;decode;;;Argument[0];ReturnValue;taint;manual",
"org.apache.shiro.codec;Base64;false;decode;(String);;Argument[0];ReturnValue;taint;manual",
"org.apache.commons.codec;Encoder;true;encode;(Object);;Argument[0];ReturnValue;taint;manual",
"org.apache.commons.codec;Decoder;true;decode;(Object);;Argument[0];ReturnValue;taint;manual",
"org.apache.commons.codec;BinaryEncoder;true;encode;(byte[]);;Argument[0];ReturnValue;taint;manual",
"org.apache.commons.codec;BinaryDecoder;true;decode;(byte[]);;Argument[0];ReturnValue;taint;manual",
"org.apache.commons.codec;StringEncoder;true;encode;(String);;Argument[0];ReturnValue;taint;manual",
"org.apache.commons.codec;StringDecoder;true;decode;(String);;Argument[0];ReturnValue;taint;manual",
"java.net;URLDecoder;false;decode;;;Argument[0];ReturnValue;taint;manual",
"java.net;URI;false;create;;;Argument[0];ReturnValue;taint;manual",
"javax.xml.transform.sax;SAXSource;false;sourceToInputSource;;;Argument[0];ReturnValue;taint;manual",
// arg to arg
"java.lang;System;false;arraycopy;;;Argument[0];Argument[2];taint;manual",
// constructor flow
"java.io;File;false;File;;;Argument[0];Argument[-1];taint;manual",
"java.io;File;false;File;;;Argument[1];Argument[-1];taint;manual",
"java.net;URI;false;URI;(String);;Argument[0];Argument[-1];taint;manual",
"java.net;URL;false;URL;(String);;Argument[0];Argument[-1];taint;manual",
"javax.xml.transform.stream;StreamSource;false;StreamSource;;;Argument[0];Argument[-1];taint;manual",
"javax.xml.transform.sax;SAXSource;false;SAXSource;(InputSource);;Argument[0];Argument[-1];taint;manual",
"javax.xml.transform.sax;SAXSource;false;SAXSource;(XMLReader,InputSource);;Argument[1];Argument[-1];taint;manual",
"org.xml.sax;InputSource;false;InputSource;;;Argument[0];Argument[-1];taint;manual",
"javax.servlet.http;Cookie;false;Cookie;;;Argument[0];Argument[-1];taint;manual",
"javax.servlet.http;Cookie;false;Cookie;;;Argument[1];Argument[-1];taint;manual",
"java.util.zip;ZipInputStream;false;ZipInputStream;;;Argument[0];Argument[-1];taint;manual",
"java.util.zip;GZIPInputStream;false;GZIPInputStream;;;Argument[0];Argument[-1];taint;manual",
"java.util;StringTokenizer;false;StringTokenizer;;;Argument[0];Argument[-1];taint;manual",
"java.beans;XMLDecoder;false;XMLDecoder;;;Argument[0];Argument[-1];taint;manual",
"com.esotericsoftware.kryo.io;Input;false;Input;;;Argument[0];Argument[-1];taint;manual",
"com.esotericsoftware.kryo5.io;Input;false;Input;;;Argument[0];Argument[-1];taint;manual",
"java.io;BufferedInputStream;false;BufferedInputStream;;;Argument[0];Argument[-1];taint;manual",
"java.io;DataInputStream;false;DataInputStream;;;Argument[0];Argument[-1];taint;manual",
"java.io;ByteArrayInputStream;false;ByteArrayInputStream;;;Argument[0];Argument[-1];taint;manual",
"java.io;ObjectInputStream;false;ObjectInputStream;;;Argument[0];Argument[-1];taint;manual",
"java.io;StringReader;false;StringReader;;;Argument[0];Argument[-1];taint;manual",
"java.io;CharArrayReader;false;CharArrayReader;;;Argument[0];Argument[-1];taint;manual",
"java.io;BufferedReader;false;BufferedReader;;;Argument[0];Argument[-1];taint;manual",
"java.io;InputStreamReader;false;InputStreamReader;;;Argument[0];Argument[-1];taint;manual",
"java.io;OutputStream;true;write;(byte[]);;Argument[0];Argument[-1];taint;manual",
"java.io;OutputStream;true;write;(byte[],int,int);;Argument[0];Argument[-1];taint;manual",
"java.io;OutputStream;true;write;(int);;Argument[0];Argument[-1];taint;manual",
"java.io;FilterOutputStream;true;FilterOutputStream;(OutputStream);;Argument[0];Argument[-1];taint;manual"
]
}
/** /**
* A unit class for adding additional source model rows. * A unit class for adding additional source model rows.
* *
@@ -410,21 +184,262 @@ class SummaryModelCsv extends Unit {
abstract predicate row(string row); abstract predicate row(string row);
} }
private predicate sourceModel(string row) { /**
sourceModelCsv(row) or * A unit class for adding negative summary model rows.
any(SourceModelCsv s).row(row) *
* Extend this class to add additional flow summary definitions.
*/
class NegativeSummaryModelCsv extends Unit {
/** Holds if `row` specifies a negative summary definition. */
abstract predicate row(string row);
} }
private predicate sinkModel(string row) { private class SourceModelCsvBase extends SourceModelCsv {
sinkModelCsv(row) or override predicate row(string row) {
any(SinkModelCsv s).row(row) row =
[
// org.springframework.security.web.savedrequest.SavedRequest
"org.springframework.security.web.savedrequest;SavedRequest;true;getRedirectUrl;;;ReturnValue;remote;manual",
"org.springframework.security.web.savedrequest;SavedRequest;true;getCookies;;;ReturnValue;remote;manual",
"org.springframework.security.web.savedrequest;SavedRequest;true;getHeaderValues;;;ReturnValue;remote;manual",
"org.springframework.security.web.savedrequest;SavedRequest;true;getHeaderNames;;;ReturnValue;remote;manual",
"org.springframework.security.web.savedrequest;SavedRequest;true;getParameterValues;;;ReturnValue;remote;manual",
"org.springframework.security.web.savedrequest;SavedRequest;true;getParameterMap;;;ReturnValue;remote;manual",
// ServletRequestGetParameterMethod
"javax.servlet;ServletRequest;false;getParameter;(String);;ReturnValue;remote;manual",
"javax.servlet;ServletRequest;false;getParameterValues;(String);;ReturnValue;remote;manual",
"javax.servlet.http;HttpServletRequest;false;getParameter;(String);;ReturnValue;remote;manual",
"javax.servlet.http;HttpServletRequest;false;getParameterValues;(String);;ReturnValue;remote;manual",
// ServletRequestGetParameterMapMethod
"javax.servlet;ServletRequest;false;getParameterMap;();;ReturnValue;remote;manual",
"javax.servlet.http;HttpServletRequest;false;getParameterMap;();;ReturnValue;remote;manual",
// ServletRequestGetParameterNamesMethod
"javax.servlet;ServletRequest;false;getParameterNames;();;ReturnValue;remote;manual",
"javax.servlet.http;HttpServletRequest;false;getParameterNames;();;ReturnValue;remote;manual",
// HttpServletRequestGetQueryStringMethod
"javax.servlet.http;HttpServletRequest;false;getQueryString;();;ReturnValue;remote;manual",
//
// URLConnectionGetInputStreamMethod
"java.net;URLConnection;false;getInputStream;();;ReturnValue;remote;manual",
// SocketGetInputStreamMethod
"java.net;Socket;false;getInputStream;();;ReturnValue;remote;manual",
// BeanValidationSource
"javax.validation;ConstraintValidator;true;isValid;;;Parameter[0];remote;manual",
// SpringMultipartRequestSource
"org.springframework.web.multipart;MultipartRequest;true;getFile;(String);;ReturnValue;remote;manual",
"org.springframework.web.multipart;MultipartRequest;true;getFileMap;();;ReturnValue;remote;manual",
"org.springframework.web.multipart;MultipartRequest;true;getFileNames;();;ReturnValue;remote;manual",
"org.springframework.web.multipart;MultipartRequest;true;getFiles;(String);;ReturnValue;remote;manual",
"org.springframework.web.multipart;MultipartRequest;true;getMultiFileMap;();;ReturnValue;remote;manual",
"org.springframework.web.multipart;MultipartRequest;true;getMultipartContentType;(String);;ReturnValue;remote;manual",
// SpringMultipartFileSource
"org.springframework.web.multipart;MultipartFile;true;getBytes;();;ReturnValue;remote;manual",
"org.springframework.web.multipart;MultipartFile;true;getContentType;();;ReturnValue;remote;manual",
"org.springframework.web.multipart;MultipartFile;true;getInputStream;();;ReturnValue;remote;manual",
"org.springframework.web.multipart;MultipartFile;true;getName;();;ReturnValue;remote;manual",
"org.springframework.web.multipart;MultipartFile;true;getOriginalFilename;();;ReturnValue;remote;manual",
"org.springframework.web.multipart;MultipartFile;true;getResource;();;ReturnValue;remote;manual",
// HttpServletRequest.get*
"javax.servlet.http;HttpServletRequest;false;getHeader;(String);;ReturnValue;remote;manual",
"javax.servlet.http;HttpServletRequest;false;getHeaders;(String);;ReturnValue;remote;manual",
"javax.servlet.http;HttpServletRequest;false;getHeaderNames;();;ReturnValue;remote;manual",
"javax.servlet.http;HttpServletRequest;false;getPathInfo;();;ReturnValue;remote;manual",
"javax.servlet.http;HttpServletRequest;false;getRequestURI;();;ReturnValue;remote;manual",
"javax.servlet.http;HttpServletRequest;false;getRequestURL;();;ReturnValue;remote;manual",
"javax.servlet.http;HttpServletRequest;false;getRemoteUser;();;ReturnValue;remote;manual",
// SpringWebRequestGetMethod
"org.springframework.web.context.request;WebRequest;false;getDescription;;;ReturnValue;remote;manual",
"org.springframework.web.context.request;WebRequest;false;getHeader;;;ReturnValue;remote;manual",
"org.springframework.web.context.request;WebRequest;false;getHeaderNames;;;ReturnValue;remote;manual",
"org.springframework.web.context.request;WebRequest;false;getHeaderValues;;;ReturnValue;remote;manual",
"org.springframework.web.context.request;WebRequest;false;getParameter;;;ReturnValue;remote;manual",
"org.springframework.web.context.request;WebRequest;false;getParameterMap;;;ReturnValue;remote;manual",
"org.springframework.web.context.request;WebRequest;false;getParameterNames;;;ReturnValue;remote;manual",
"org.springframework.web.context.request;WebRequest;false;getParameterValues;;;ReturnValue;remote;manual",
// TODO consider org.springframework.web.context.request.WebRequest.getRemoteUser
// ServletRequestGetBodyMethod
"javax.servlet;ServletRequest;false;getInputStream;();;ReturnValue;remote;manual",
"javax.servlet;ServletRequest;false;getReader;();;ReturnValue;remote;manual",
// CookieGet*
"javax.servlet.http;Cookie;false;getValue;();;ReturnValue;remote;manual",
"javax.servlet.http;Cookie;false;getName;();;ReturnValue;remote;manual",
"javax.servlet.http;Cookie;false;getComment;();;ReturnValue;remote;manual",
// ApacheHttp*
"org.apache.http;HttpMessage;false;getParams;();;ReturnValue;remote;manual",
"org.apache.http;HttpEntity;false;getContent;();;ReturnValue;remote;manual",
// In the setting of Android we assume that XML has been transmitted over
// the network, so may be tainted.
// XmlPullGetMethod
"org.xmlpull.v1;XmlPullParser;false;getName;();;ReturnValue;remote;manual",
"org.xmlpull.v1;XmlPullParser;false;getNamespace;();;ReturnValue;remote;manual",
"org.xmlpull.v1;XmlPullParser;false;getText;();;ReturnValue;remote;manual",
// XmlAttrSetGetMethod
"android.util;AttributeSet;false;getAttributeBooleanValue;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getAttributeCount;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getAttributeFloatValue;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getAttributeIntValue;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getAttributeListValue;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getAttributeName;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getAttributeNameResource;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getAttributeNamespace;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getAttributeResourceValue;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getAttributeUnsignedIntValue;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getAttributeValue;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getClassAttribute;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getIdAttribute;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getIdAttributeResourceValue;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getPositionDescription;;;ReturnValue;remote;manual",
"android.util;AttributeSet;false;getStyleAttribute;;;ReturnValue;remote;manual",
// The current URL in a browser may be untrusted or uncontrolled.
// WebViewGetUrlMethod
"android.webkit;WebView;false;getUrl;();;ReturnValue;remote;manual",
"android.webkit;WebView;false;getOriginalUrl;();;ReturnValue;remote;manual",
// SpringRestTemplateResponseEntityMethod
"org.springframework.web.client;RestTemplate;false;exchange;;;ReturnValue;remote;manual",
"org.springframework.web.client;RestTemplate;false;getForEntity;;;ReturnValue;remote;manual",
"org.springframework.web.client;RestTemplate;false;postForEntity;;;ReturnValue;remote;manual",
// WebSocketMessageParameterSource
"java.net.http;WebSocket$Listener;true;onText;(WebSocket,CharSequence,boolean);;Parameter[1];remote;manual",
// PlayRequestGetMethod
"play.mvc;Http$RequestHeader;false;queryString;;;ReturnValue;remote;manual",
"play.mvc;Http$RequestHeader;false;getQueryString;;;ReturnValue;remote;manual",
"play.mvc;Http$RequestHeader;false;header;;;ReturnValue;remote;manual",
"play.mvc;Http$RequestHeader;false;getHeader;;;ReturnValue;remote;manual"
]
}
} }
private predicate summaryModel(string row) { private class SinkModelCsvBase extends SinkModelCsv {
summaryModelCsv(row) or override predicate row(string row) {
any(SummaryModelCsv s).row(row) row =
[
// Open URL
"java.net;URL;false;openConnection;;;Argument[-1];open-url;manual",
"java.net;URL;false;openStream;;;Argument[-1];open-url;manual",
"java.net.http;HttpRequest;false;newBuilder;;;Argument[0];open-url;manual",
"java.net.http;HttpRequest$Builder;false;uri;;;Argument[0];open-url;manual",
"java.net;URLClassLoader;false;URLClassLoader;(URL[]);;Argument[0];open-url;manual",
"java.net;URLClassLoader;false;URLClassLoader;(URL[],ClassLoader);;Argument[0];open-url;manual",
"java.net;URLClassLoader;false;URLClassLoader;(URL[],ClassLoader,URLStreamHandlerFactory);;Argument[0];open-url;manual",
"java.net;URLClassLoader;false;URLClassLoader;(String,URL[],ClassLoader);;Argument[1];open-url;manual",
"java.net;URLClassLoader;false;URLClassLoader;(String,URL[],ClassLoader,URLStreamHandlerFactory);;Argument[1];open-url;manual",
"java.net;URLClassLoader;false;newInstance;;;Argument[0];open-url;manual",
// Bean validation
"javax.validation;ConstraintValidatorContext;true;buildConstraintViolationWithTemplate;;;Argument[0];bean-validation;manual",
// Set hostname
"javax.net.ssl;HttpsURLConnection;true;setDefaultHostnameVerifier;;;Argument[0];set-hostname-verifier;manual",
"javax.net.ssl;HttpsURLConnection;true;setHostnameVerifier;;;Argument[0];set-hostname-verifier;manual"
]
}
} }
private class SummaryModelCsvBase extends SummaryModelCsv {
override predicate row(string row) {
row =
[
// qualifier to arg
"java.io;InputStream;true;read;(byte[]);;Argument[-1];Argument[0];taint;manual",
"java.io;InputStream;true;read;(byte[],int,int);;Argument[-1];Argument[0];taint;manual",
"java.io;InputStream;true;readNBytes;(byte[],int,int);;Argument[-1];Argument[0];taint;manual",
"java.io;InputStream;true;transferTo;(OutputStream);;Argument[-1];Argument[0];taint;manual",
"java.io;ByteArrayOutputStream;false;writeTo;;;Argument[-1];Argument[0];taint;manual",
"java.io;Reader;true;read;;;Argument[-1];Argument[0];taint;manual",
// qualifier to return
"java.io;ByteArrayOutputStream;false;toByteArray;;;Argument[-1];ReturnValue;taint;manual",
"java.io;ByteArrayOutputStream;false;toString;;;Argument[-1];ReturnValue;taint;manual",
"java.io;InputStream;true;readAllBytes;;;Argument[-1];ReturnValue;taint;manual",
"java.io;InputStream;true;readNBytes;(int);;Argument[-1];ReturnValue;taint;manual",
"java.util;StringTokenizer;false;nextElement;();;Argument[-1];ReturnValue;taint;manual",
"java.util;StringTokenizer;false;nextToken;;;Argument[-1];ReturnValue;taint;manual",
"javax.xml.transform.sax;SAXSource;false;getInputSource;;;Argument[-1];ReturnValue;taint;manual",
"javax.xml.transform.stream;StreamSource;false;getInputStream;;;Argument[-1];ReturnValue;taint;manual",
"java.nio;ByteBuffer;false;get;;;Argument[-1];ReturnValue;taint;manual",
"java.net;URI;false;toURL;;;Argument[-1];ReturnValue;taint;manual",
"java.net;URI;false;toString;;;Argument[-1];ReturnValue;taint;manual",
"java.net;URI;false;toAsciiString;;;Argument[-1];ReturnValue;taint;manual",
"java.io;File;true;toURI;;;Argument[-1];ReturnValue;taint;manual",
"java.io;File;true;toPath;;;Argument[-1];ReturnValue;taint;manual",
"java.io;File;true;getAbsoluteFile;;;Argument[-1];ReturnValue;taint;manual",
"java.io;File;true;getCanonicalFile;;;Argument[-1];ReturnValue;taint;manual",
"java.io;File;true;getAbsolutePath;;;Argument[-1];ReturnValue;taint;manual",
"java.io;File;true;getCanonicalPath;;;Argument[-1];ReturnValue;taint;manual",
"java.nio;ByteBuffer;false;array;();;Argument[-1];ReturnValue;taint;manual",
"java.nio.file;Path;true;normalize;;;Argument[-1];ReturnValue;taint;manual",
"java.nio.file;Path;true;resolve;;;Argument[-1..0];ReturnValue;taint;manual",
"java.nio.file;Path;false;toFile;;;Argument[-1];ReturnValue;taint;manual",
"java.nio.file;Path;true;toString;;;Argument[-1];ReturnValue;taint;manual",
"java.nio.file;Path;true;toUri;;;Argument[-1];ReturnValue;taint;manual",
"java.nio.file;Paths;true;get;;;Argument[0..1];ReturnValue;taint;manual",
"java.io;BufferedReader;true;readLine;;;Argument[-1];ReturnValue;taint;manual",
"java.io;Reader;true;read;();;Argument[-1];ReturnValue;taint;manual",
// arg to return
"java.nio;ByteBuffer;false;wrap;(byte[]);;Argument[0];ReturnValue;taint;manual",
"java.util;Base64$Encoder;false;encode;(byte[]);;Argument[0];ReturnValue;taint;manual",
"java.util;Base64$Encoder;false;encode;(ByteBuffer);;Argument[0];ReturnValue;taint;manual",
"java.util;Base64$Encoder;false;encodeToString;(byte[]);;Argument[0];ReturnValue;taint;manual",
"java.util;Base64$Encoder;false;wrap;(OutputStream);;Argument[0];ReturnValue;taint;manual",
"java.util;Base64$Decoder;false;decode;(byte[]);;Argument[0];ReturnValue;taint;manual",
"java.util;Base64$Decoder;false;decode;(ByteBuffer);;Argument[0];ReturnValue;taint;manual",
"java.util;Base64$Decoder;false;decode;(String);;Argument[0];ReturnValue;taint;manual",
"java.util;Base64$Decoder;false;wrap;(InputStream);;Argument[0];ReturnValue;taint;manual",
"cn.hutool.core.codec;Base64;true;decode;;;Argument[0];ReturnValue;taint;manual",
"org.apache.shiro.codec;Base64;false;decode;(String);;Argument[0];ReturnValue;taint;manual",
"org.apache.commons.codec;Encoder;true;encode;(Object);;Argument[0];ReturnValue;taint;manual",
"org.apache.commons.codec;Decoder;true;decode;(Object);;Argument[0];ReturnValue;taint;manual",
"org.apache.commons.codec;BinaryEncoder;true;encode;(byte[]);;Argument[0];ReturnValue;taint;manual",
"org.apache.commons.codec;BinaryDecoder;true;decode;(byte[]);;Argument[0];ReturnValue;taint;manual",
"org.apache.commons.codec;StringEncoder;true;encode;(String);;Argument[0];ReturnValue;taint;manual",
"org.apache.commons.codec;StringDecoder;true;decode;(String);;Argument[0];ReturnValue;taint;manual",
"java.net;URLDecoder;false;decode;;;Argument[0];ReturnValue;taint;manual",
"java.net;URI;false;create;;;Argument[0];ReturnValue;taint;manual",
"javax.xml.transform.sax;SAXSource;false;sourceToInputSource;;;Argument[0];ReturnValue;taint;manual",
// arg to arg
"java.lang;System;false;arraycopy;;;Argument[0];Argument[2];taint;manual",
// constructor flow
"java.io;File;false;File;;;Argument[0];Argument[-1];taint;manual",
"java.io;File;false;File;;;Argument[1];Argument[-1];taint;manual",
"java.net;URI;false;URI;(String);;Argument[0];Argument[-1];taint;manual",
"java.net;URL;false;URL;(String);;Argument[0];Argument[-1];taint;manual",
"javax.xml.transform.stream;StreamSource;false;StreamSource;;;Argument[0];Argument[-1];taint;manual",
"javax.xml.transform.sax;SAXSource;false;SAXSource;(InputSource);;Argument[0];Argument[-1];taint;manual",
"javax.xml.transform.sax;SAXSource;false;SAXSource;(XMLReader,InputSource);;Argument[1];Argument[-1];taint;manual",
"org.xml.sax;InputSource;false;InputSource;;;Argument[0];Argument[-1];taint;manual",
"javax.servlet.http;Cookie;false;Cookie;;;Argument[0];Argument[-1];taint;manual",
"javax.servlet.http;Cookie;false;Cookie;;;Argument[1];Argument[-1];taint;manual",
"java.util.zip;ZipInputStream;false;ZipInputStream;;;Argument[0];Argument[-1];taint;manual",
"java.util.zip;GZIPInputStream;false;GZIPInputStream;;;Argument[0];Argument[-1];taint;manual",
"java.util;StringTokenizer;false;StringTokenizer;;;Argument[0];Argument[-1];taint;manual",
"java.beans;XMLDecoder;false;XMLDecoder;;;Argument[0];Argument[-1];taint;manual",
"com.esotericsoftware.kryo.io;Input;false;Input;;;Argument[0];Argument[-1];taint;manual",
"com.esotericsoftware.kryo5.io;Input;false;Input;;;Argument[0];Argument[-1];taint;manual",
"java.io;BufferedInputStream;false;BufferedInputStream;;;Argument[0];Argument[-1];taint;manual",
"java.io;DataInputStream;false;DataInputStream;;;Argument[0];Argument[-1];taint;manual",
"java.io;ByteArrayInputStream;false;ByteArrayInputStream;;;Argument[0];Argument[-1];taint;manual",
"java.io;ObjectInputStream;false;ObjectInputStream;;;Argument[0];Argument[-1];taint;manual",
"java.io;StringReader;false;StringReader;;;Argument[0];Argument[-1];taint;manual",
"java.io;CharArrayReader;false;CharArrayReader;;;Argument[0];Argument[-1];taint;manual",
"java.io;BufferedReader;false;BufferedReader;;;Argument[0];Argument[-1];taint;manual",
"java.io;InputStreamReader;false;InputStreamReader;;;Argument[0];Argument[-1];taint;manual",
"java.io;OutputStream;true;write;(byte[]);;Argument[0];Argument[-1];taint;manual",
"java.io;OutputStream;true;write;(byte[],int,int);;Argument[0];Argument[-1];taint;manual",
"java.io;OutputStream;true;write;(int);;Argument[0];Argument[-1];taint;manual",
"java.io;FilterOutputStream;true;FilterOutputStream;(OutputStream);;Argument[0];Argument[-1];taint;manual"
]
}
}
/** Holds if `row` is a source model. */
predicate sourceModel(string row) { any(SourceModelCsv s).row(row) }
/** Holds if `row` is a sink model. */
predicate sinkModel(string row) { any(SinkModelCsv s).row(row) }
/** Holds if `row` is a summary model. */
predicate summaryModel(string row) { any(SummaryModelCsv s).row(row) }
/** Holds if `row` is negative summary model. */
predicate negativeSummaryModel(string row) { any(NegativeSummaryModelCsv s).row(row) }
/** Holds if a source model exists for the given parameters. */ /** Holds if a source model exists for the given parameters. */
predicate sourceModel( predicate sourceModel(
string namespace, string type, boolean subtypes, string name, string signature, string ext, string namespace, string type, boolean subtypes, string name, string signature, string ext,
@@ -492,6 +507,20 @@ predicate summaryModel(
row.splitAt(";", 9) = provenance row.splitAt(";", 9) = provenance
} }
/** Holds if a summary model exists indicating there is no flow for the given parameters. */
predicate negativeSummaryModel(
string namespace, string type, string name, string signature, string provenance
) {
exists(string row |
negativeSummaryModel(row) and
row.splitAt(";", 0) = namespace and
row.splitAt(";", 1) = type and
row.splitAt(";", 2) = name and
row.splitAt(";", 3) = signature and
row.splitAt(";", 4) = provenance
)
}
private predicate relevantPackage(string package) { private predicate relevantPackage(string package) {
sourceModel(package, _, _, _, _, _, _, _, _) or sourceModel(package, _, _, _, _, _, _, _, _) or
sinkModel(package, _, _, _, _, _, _, _, _) or sinkModel(package, _, _, _, _, _, _, _, _) or
@@ -548,38 +577,7 @@ predicate modelCoverage(string package, int pkgs, string kind, string part, int
/** Provides a query predicate to check the CSV data for validation errors. */ /** Provides a query predicate to check the CSV data for validation errors. */
module CsvValidation { module CsvValidation {
/** Holds if some row in a CSV-based flow model appears to contain typos. */ private string getInvalidModelInput() {
query predicate invalidModelRow(string msg) {
exists(
string pred, string namespace, string type, string name, string signature, string ext,
string provenance
|
sourceModel(namespace, type, _, name, signature, ext, _, _, provenance) and pred = "source"
or
sinkModel(namespace, type, _, name, signature, ext, _, _, provenance) and pred = "sink"
or
summaryModel(namespace, type, _, name, signature, ext, _, _, _, provenance) and
pred = "summary"
|
not namespace.regexpMatch("[a-zA-Z0-9_\\.]+") and
msg = "Dubious namespace \"" + namespace + "\" in " + pred + " model."
or
not type.regexpMatch("[a-zA-Z0-9_\\$<>]+") and
msg = "Dubious type \"" + type + "\" in " + pred + " model."
or
not name.regexpMatch("[a-zA-Z0-9_]*") and
msg = "Dubious name \"" + name + "\" in " + pred + " model."
or
not signature.regexpMatch("|\\([a-zA-Z0-9_\\.\\$<>,\\[\\]]*\\)") and
msg = "Dubious signature \"" + signature + "\" in " + pred + " model."
or
not ext.regexpMatch("|Annotated") and
msg = "Unrecognized extra API graph element \"" + ext + "\" in " + pred + " model."
or
not provenance = ["manual", "generated"] and
msg = "Unrecognized provenance description \"" + provenance + "\" in " + pred + " model."
)
or
exists(string pred, string input, string part | exists(string pred, string input, string part |
sinkModel(_, _, _, _, _, _, input, _, _) and pred = "sink" sinkModel(_, _, _, _, _, _, input, _, _) and pred = "sink"
or or
@@ -594,9 +592,11 @@ module CsvValidation {
part = input.(AccessPath).getToken(0) and part = input.(AccessPath).getToken(0) and
parseParam(part, _) parseParam(part, _)
) and ) and
msg = "Unrecognized input specification \"" + part + "\" in " + pred + " model." result = "Unrecognized input specification \"" + part + "\" in " + pred + " model."
) )
or }
private string getInvalidModelOutput() {
exists(string pred, string output, string part | exists(string pred, string output, string part |
sourceModel(_, _, _, _, _, _, output, _, _) and pred = "source" sourceModel(_, _, _, _, _, _, output, _, _) and pred = "source"
or or
@@ -605,35 +605,15 @@ module CsvValidation {
invalidSpecComponent(output, part) and invalidSpecComponent(output, part) and
not part = "" and not part = "" and
not (part = ["Argument", "Parameter"] and pred = "source") and not (part = ["Argument", "Parameter"] and pred = "source") and
msg = "Unrecognized output specification \"" + part + "\" in " + pred + " model." result = "Unrecognized output specification \"" + part + "\" in " + pred + " model."
) )
or }
exists(string pred, string row, int expect |
sourceModel(row) and expect = 9 and pred = "source" private string getInvalidModelKind() {
or
sinkModel(row) and expect = 9 and pred = "sink"
or
summaryModel(row) and expect = 10 and pred = "summary"
|
exists(int cols |
cols = 1 + max(int n | exists(row.splitAt(";", n))) and
cols != expect and
msg =
"Wrong number of columns in " + pred + " model row, expected " + expect + ", got " + cols +
" in " + row + "."
)
or
exists(string b |
b = row.splitAt(";", 2) and
not b = ["true", "false"] and
msg = "Invalid boolean \"" + b + "\" in " + pred + " model."
)
)
or
exists(string row, string kind | summaryModel(row) | exists(string row, string kind | summaryModel(row) |
kind = row.splitAt(";", 8) and kind = row.splitAt(";", 8) and
not kind = ["taint", "value"] and not kind = ["taint", "value"] and
msg = "Invalid kind \"" + kind + "\" in summary model." result = "Invalid kind \"" + kind + "\" in summary model."
) )
or or
exists(string row, string kind | sinkModel(row) | exists(string row, string kind | sinkModel(row) |
@@ -647,25 +627,110 @@ module CsvValidation {
] and ] and
not kind.matches("regex-use%") and not kind.matches("regex-use%") and
not kind.matches("qltest%") and not kind.matches("qltest%") and
msg = "Invalid kind \"" + kind + "\" in sink model." result = "Invalid kind \"" + kind + "\" in sink model."
) )
or or
exists(string row, string kind | sourceModel(row) | exists(string row, string kind | sourceModel(row) |
kind = row.splitAt(";", 7) and kind = row.splitAt(";", 7) and
not kind = ["remote", "contentprovider", "android-widget", "android-external-storage-dir"] and not kind = ["remote", "contentprovider", "android-widget", "android-external-storage-dir"] and
not kind.matches("qltest%") and not kind.matches("qltest%") and
msg = "Invalid kind \"" + kind + "\" in source model." result = "Invalid kind \"" + kind + "\" in source model."
) )
} }
private string getInvalidModelSubtype() {
exists(string pred, string row |
sourceModel(row) and pred = "source"
or
sinkModel(row) and pred = "sink"
or
summaryModel(row) and pred = "summary"
|
exists(string b |
b = row.splitAt(";", 2) and
not b = ["true", "false"] and
result = "Invalid boolean \"" + b + "\" in " + pred + " model."
)
)
}
private string getInvalidModelColumnCount() {
exists(string pred, string row, int expect |
sourceModel(row) and expect = 9 and pred = "source"
or
sinkModel(row) and expect = 9 and pred = "sink"
or
summaryModel(row) and expect = 10 and pred = "summary"
or
negativeSummaryModel(row) and expect = 5 and pred = "negative summary"
|
exists(int cols |
cols = 1 + max(int n | exists(row.splitAt(";", n))) and
cols != expect and
result =
"Wrong number of columns in " + pred + " model row, expected " + expect + ", got " + cols +
" in " + row + "."
)
)
}
private string getInvalidModelSignature() {
exists(
string pred, string namespace, string type, string name, string signature, string ext,
string provenance
|
sourceModel(namespace, type, _, name, signature, ext, _, _, provenance) and pred = "source"
or
sinkModel(namespace, type, _, name, signature, ext, _, _, provenance) and pred = "sink"
or
summaryModel(namespace, type, _, name, signature, ext, _, _, _, provenance) and
pred = "summary"
or
negativeSummaryModel(namespace, type, name, signature, provenance) and
ext = "" and
pred = "negative summary"
|
not namespace.regexpMatch("[a-zA-Z0-9_\\.]+") and
result = "Dubious namespace \"" + namespace + "\" in " + pred + " model."
or
not type.regexpMatch("[a-zA-Z0-9_\\$<>]+") and
result = "Dubious type \"" + type + "\" in " + pred + " model."
or
not name.regexpMatch("[a-zA-Z0-9_]*") and
result = "Dubious name \"" + name + "\" in " + pred + " model."
or
not signature.regexpMatch("|\\([a-zA-Z0-9_\\.\\$<>,\\[\\]]*\\)") and
result = "Dubious signature \"" + signature + "\" in " + pred + " model."
or
not ext.regexpMatch("|Annotated") and
result = "Unrecognized extra API graph element \"" + ext + "\" in " + pred + " model."
or
not provenance = ["manual", "generated"] and
result = "Unrecognized provenance description \"" + provenance + "\" in " + pred + " model."
)
}
/** Holds if some row in a CSV-based flow model appears to contain typos. */
query predicate invalidModelRow(string msg) {
msg =
[
getInvalidModelSignature(), getInvalidModelInput(), getInvalidModelOutput(),
getInvalidModelSubtype(), getInvalidModelColumnCount(), getInvalidModelKind()
]
}
} }
pragma[nomagic] pragma[nomagic]
private predicate elementSpec( private predicate elementSpec(
string namespace, string type, boolean subtypes, string name, string signature, string ext string namespace, string type, boolean subtypes, string name, string signature, string ext
) { ) {
sourceModel(namespace, type, subtypes, name, signature, ext, _, _, _) or sourceModel(namespace, type, subtypes, name, signature, ext, _, _, _)
sinkModel(namespace, type, subtypes, name, signature, ext, _, _, _) or or
sinkModel(namespace, type, subtypes, name, signature, ext, _, _, _)
or
summaryModel(namespace, type, subtypes, name, signature, ext, _, _, _, _) summaryModel(namespace, type, subtypes, name, signature, ext, _, _, _, _)
or
negativeSummaryModel(namespace, type, name, signature, _) and ext = "" and subtypes = false
} }
private string paramsStringPart(Callable c, int i) { private string paramsStringPart(Callable c, int i) {
@@ -714,7 +779,7 @@ private Element interpretElement0(
) )
} }
/** Gets the source/sink/summary element corresponding to the supplied parameters. */ /** Gets the source/sink/summary/negativesummary element corresponding to the supplied parameters. */
Element interpretElement( Element interpretElement(
string namespace, string type, boolean subtypes, string name, string signature, string ext string namespace, string type, boolean subtypes, string name, string signature, string ext
) { ) {

View File

@@ -240,6 +240,16 @@ module Public {
*/ */
predicate isAutoGenerated() { none() } predicate isAutoGenerated() { none() }
} }
/** A callable with a flow summary stating there is no flow via the callable. */
class NegativeSummarizedCallable extends SummarizedCallableBase {
NegativeSummarizedCallable() { negativeSummaryElement(this, _) }
/**
* Holds if the negative summary is auto generated.
*/
predicate isAutoGenerated() { negativeSummaryElement(this, true) }
}
} }
/** /**
@@ -1094,7 +1104,7 @@ module Private {
/** Provides a query predicate for outputting a set of relevant flow summaries. */ /** Provides a query predicate for outputting a set of relevant flow summaries. */
module TestOutput { module TestOutput {
/** A flow summary to include in the `summary/3` query predicate. */ /** A flow summary to include in the `summary/1` query predicate. */
abstract class RelevantSummarizedCallable instanceof SummarizedCallable { abstract class RelevantSummarizedCallable instanceof SummarizedCallable {
/** Gets the string representation of this callable used by `summary/1`. */ /** Gets the string representation of this callable used by `summary/1`. */
abstract string getCallableCsv(); abstract string getCallableCsv();
@@ -1109,6 +1119,14 @@ module Private {
string toString() { result = super.toString() } string toString() { result = super.toString() }
} }
/** A flow summary to include in the `negativeSummary/1` query predicate. */
abstract class RelevantNegativeSummarizedCallable instanceof NegativeSummarizedCallable {
/** Gets the string representation of this callable used by `summary/1`. */
abstract string getCallableCsv();
string toString() { result = super.toString() }
}
/** Render the kind in the format used in flow summaries. */ /** Render the kind in the format used in flow summaries. */
private string renderKind(boolean preservesValue) { private string renderKind(boolean preservesValue) {
preservesValue = true and result = "value" preservesValue = true and result = "value"
@@ -1116,8 +1134,12 @@ module Private {
preservesValue = false and result = "taint" preservesValue = false and result = "taint"
} }
private string renderProvenance(RelevantSummarizedCallable c) { private string renderProvenance(SummarizedCallable c) {
if c.(SummarizedCallable).isAutoGenerated() then result = "generated" else result = "manual" if c.isAutoGenerated() then result = "generated" else result = "manual"
}
private string renderProvenanceNegative(NegativeSummarizedCallable c) {
if c.isAutoGenerated() then result = "generated" else result = "manual"
} }
/** /**
@@ -1132,8 +1154,23 @@ module Private {
| |
c.relevantSummary(input, output, preservesValue) and c.relevantSummary(input, output, preservesValue) and
csv = csv =
c.getCallableCsv() + getComponentStackCsv(input) + ";" + getComponentStackCsv(output) + c.getCallableCsv() // Callable information
";" + renderKind(preservesValue) + ";" + renderProvenance(c) + getComponentStackCsv(input) + ";" // input
+ getComponentStackCsv(output) + ";" // output
+ renderKind(preservesValue) + ";" // kind
+ renderProvenance(c) // provenance
)
}
/**
* Holds if a negative flow summary `csv` exists (semi-colon separated format). Used for testing purposes.
* The syntax is: "namespace;type;name;signature;provenance"",
*/
query predicate negativeSummary(string csv) {
exists(RelevantNegativeSummarizedCallable c |
csv =
c.getCallableCsv() // Callable information
+ renderProvenanceNegative(c) // provenance
) )
} }
} }

View File

@@ -78,6 +78,18 @@ predicate summaryElement(Callable c, string input, string output, string kind, b
) )
} }
/**
* Holds if a negative flow summary exists for `c`, which means that there is no
* flow through `c`. The flag `generated` states whether the summary is autogenerated.
*/
predicate negativeSummaryElement(Callable c, boolean generated) {
exists(string namespace, string type, string name, string signature, string provenance |
negativeSummaryModel(namespace, type, name, signature, provenance) and
generated = isGenerated(provenance) and
c = interpretElement(namespace, type, false, name, signature, "")
)
}
/** Gets the summary component for specification component `c`, if any. */ /** Gets the summary component for specification component `c`, if any. */
bindingset[c] bindingset[c]
SummaryComponent interpretComponentSpecific(AccessPathToken c) { SummaryComponent interpretComponentSpecific(AccessPathToken c) {

View File

@@ -7,11 +7,13 @@
*/ */
import java import java
import semmle.code.java.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
import ExternalApi import ExternalApi
private predicate getRelevantUsages(ExternalApi api, int usages) { private predicate getRelevantUsages(ExternalApi api, int usages) {
not api.isUninteresting() and not api.isUninteresting() and
not api.isSupported() and not api.isSupported() and
not api instanceof FlowSummaryImpl::Public::NegativeSummarizedCallable and
usages = usages =
strictcount(Call c | strictcount(Call c |
c.getCallee().getSourceDeclaration() = api and c.getCallee().getSourceDeclaration() = api and

View File

@@ -0,0 +1,14 @@
/**
* @name Capture negative summary models.
* @description Finds negative summary models to be used by other queries.
* @kind diagnostic
* @id java/utils/model-generator/negative-summary-models
* @tags model-generator
*/
private import internal.CaptureModels
private import internal.CaptureSummaryFlow
from TargetApi api, string noflow
where noflow = captureNoFlow(api)
select noflow order by noflow

View File

@@ -7,78 +7,7 @@
*/ */
private import internal.CaptureModels private import internal.CaptureModels
private import internal.CaptureSummaryFlow
/**
* Capture fluent APIs that return `this`.
* Example of a fluent API:
* ```java
* public class Foo {
* public Foo someAPI() {
* // some side-effect
* return this;
* }
* }
* ```
*
* Capture APIs that transfer taint from an input parameter to an output return
* value or parameter.
* Allows a sequence of read steps followed by a sequence of store steps.
*
* Examples:
*
* ```java
* public class Foo {
* private String tainted;
*
* public String returnsTainted() {
* return tainted;
* }
*
* public void putsTaintIntoParameter(List<String> foo) {
* foo.add(tainted);
* }
* }
* ```
* Captured Models:
* ```
* p;Foo;true;returnsTainted;;Argument[-1];ReturnValue;taint
* p;Foo;true;putsTaintIntoParameter;(List);Argument[-1];Argument[0];taint
* ```
*
* ```java
* public class Foo {
* private String tainted;
* public void doSomething(String input) {
* tainted = input;
* }
* ```
* Captured Model:
* ```p;Foo;true;doSomething;(String);Argument[0];Argument[-1];taint```
*
* ```java
* public class Foo {
* public String returnData(String tainted) {
* return tainted.substring(0,10)
* }
* }
* ```
* Captured Model:
* ```p;Foo;true;returnData;;Argument[0];ReturnValue;taint```
*
* ```java
* public class Foo {
* public void addToList(String tainted, List<String> foo) {
* foo.add(tainted);
* }
* }
* ```
* Captured Model:
* ```p;Foo;true;addToList;;Argument[0];Argument[1];taint```
*/
string captureFlow(TargetApi api) {
result = captureQualifierFlow(api) or
result = captureThroughFlow(api)
}
from TargetApi api, string flow from TargetApi api, string flow
where flow = captureFlow(api) where flow = captureFlow(api)

View File

@@ -48,6 +48,8 @@ private string asSummaryModel(TargetApi api, string input, string output, string
+ "generated" + "generated"
} }
string asNegativeSummaryModel(TargetApi api) { result = asPartialNegativeModel(api) + "generated" }
/** /**
* Gets the value summary model for `api` with `input` and `output`. * Gets the value summary model for `api` with `input` and `output`.
*/ */

View File

@@ -98,16 +98,38 @@ private string typeAsSummaryModel(TargetApiSpecific api) {
result = typeAsModel(bestTypeForModel(api)) result = typeAsModel(bestTypeForModel(api))
} }
private predicate partialModel(TargetApiSpecific api, string type, string name, string parameters) {
type = typeAsSummaryModel(api) and
name = api.getName() and
parameters = ExternalFlow::paramsString(api)
}
/** /**
* Computes the first 6 columns for CSV rows. * Computes the first 6 columns for CSV rows.
*/ */
string asPartialModel(TargetApiSpecific api) { string asPartialModel(TargetApiSpecific api) {
result = exists(string type, string name, string parameters |
typeAsSummaryModel(api) + ";" // partialModel(api, type, name, parameters) and
+ isExtensible(bestTypeForModel(api)) + ";" // result =
+ api.getName() + ";" // type + ";" //
+ ExternalFlow::paramsString(api) + ";" // + isExtensible(bestTypeForModel(api)) + ";" //
+ /* ext + */ ";" // + name + ";" //
+ parameters + ";" //
+ /* ext + */ ";" //
)
}
/**
* Computes the first 4 columns for negative CSV rows.
*/
string asPartialNegativeModel(TargetApiSpecific api) {
exists(string type, string name, string parameters |
partialModel(api, type, name, parameters) and
result =
type + ";" //
+ name + ";" //
+ parameters + ";" //
)
} }
private predicate isPrimitiveTypeUsedForBulkData(J::Type t) { private predicate isPrimitiveTypeUsedForBulkData(J::Type t) {

View File

@@ -0,0 +1,82 @@
private import CaptureModels
/**
* Capture fluent APIs that return `this`.
* Example of a fluent API:
* ```java
* public class Foo {
* public Foo someAPI() {
* // some side-effect
* return this;
* }
* }
* ```
*
* Capture APIs that transfer taint from an input parameter to an output return
* value or parameter.
* Allows a sequence of read steps followed by a sequence of store steps.
*
* Examples:
*
* ```java
* public class Foo {
* private String tainted;
*
* public String returnsTainted() {
* return tainted;
* }
*
* public void putsTaintIntoParameter(List<String> foo) {
* foo.add(tainted);
* }
* }
* ```
* Captured Models:
* ```
* p;Foo;true;returnsTainted;;Argument[-1];ReturnValue;taint
* p;Foo;true;putsTaintIntoParameter;(List);Argument[-1];Argument[0];taint
* ```
*
* ```java
* public class Foo {
* private String tainted;
* public void doSomething(String input) {
* tainted = input;
* }
* ```
* Captured Model:
* ```p;Foo;true;doSomething;(String);Argument[0];Argument[-1];taint```
*
* ```java
* public class Foo {
* public String returnData(String tainted) {
* return tainted.substring(0,10)
* }
* }
* ```
* Captured Model:
* ```p;Foo;true;returnData;;Argument[0];ReturnValue;taint```
*
* ```java
* public class Foo {
* public void addToList(String tainted, List<String> foo) {
* foo.add(tainted);
* }
* }
* ```
* Captured Model:
* ```p;Foo;true;addToList;;Argument[0];Argument[1];taint```
*/
string captureFlow(TargetApi api) {
result = captureQualifierFlow(api) or
result = captureThroughFlow(api)
}
/**
* Gets the negative summary for `api`, if any.
* A negative summary is generated, if there does not exist any positive flow.
*/
string captureNoFlow(TargetApi api) {
not exists(captureFlow(api)) and
result = asNegativeSummaryModel(api)
}

View File

@@ -1,8 +1,8 @@
import java import java
import semmle.code.java.dataflow.DataFlow import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.ExternalFlow import semmle.code.java.dataflow.ExternalFlow
import semmle.code.java.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
import CsvValidation import CsvValidation
import semmle.code.java.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
class SummaryModelTest extends SummaryModelCsv { class SummaryModelTest extends SummaryModelCsv {
override predicate row(string row) { override predicate row(string row) {

View File

@@ -0,0 +1,44 @@
| p;AbstractImplOfExternalSPI;AbstractImplOfExternalSPI;();generated |
| p;Factory;getIntValue;();generated |
| p;FinalClass;FinalClass;();generated |
| p;FinalClass;returnsConstant;();generated |
| p;FluentAPI$Inner;Inner;();generated |
| p;FluentAPI$Inner;notThis;(String);generated |
| p;FluentAPI;FluentAPI;();generated |
| p;ImmutablePojo;getX;();generated |
| p;ImplOfExternalSPI;ImplOfExternalSPI;();generated |
| p;InnerClasses$CaptureMe;CaptureMe;();generated |
| p;InnerClasses;InnerClasses;();generated |
| p;InnerHolder;InnerHolder;();generated |
| p;Joiner;length;();generated |
| p;MultipleImpls$Strat1;Strat1;();generated |
| p;MultipleImpls$Strat2;Strat2;();generated |
| p;MultipleImpls$Strat3;Strat3;();generated |
| p;MultipleImpls$Strategy;doSomething;(String);generated |
| p;MultipleImpls;MultipleImpls;();generated |
| p;ParamFlow;ParamFlow;();generated |
| p;ParamFlow;ignorePrimitiveReturnValue;(String);generated |
| p;ParamFlow;mapType;(Class);generated |
| p;Pojo;Pojo;();generated |
| p;Pojo;doNotSetValue;(String);generated |
| p;Pojo;getBigDecimal;();generated |
| p;Pojo;getBigInt;();generated |
| p;Pojo;getBoxedArray;();generated |
| p;Pojo;getBoxedCollection;();generated |
| p;Pojo;getBoxedValue;();generated |
| p;Pojo;getFloatArray;();generated |
| p;Pojo;getIntValue;();generated |
| p;Pojo;getPrimitiveArray;();generated |
| p;PrivateFlowViaPublicInterface$SPI;openStream;();generated |
| p;PrivateFlowViaPublicInterface$SPI;openStreamNone;();generated |
| p;PrivateFlowViaPublicInterface;PrivateFlowViaPublicInterface;();generated |
| p;PrivateFlowViaPublicInterface;createAnSPIWithoutTrackingFile;(File);generated |
| p;Sinks;Sinks;();generated |
| p;Sinks;copyFileToDirectory;(Path,Path,CopyOption[]);generated |
| p;Sinks;propagate;(String);generated |
| p;Sinks;readUrl;(URL,Charset);generated |
| p;Sources;Sources;();generated |
| p;Sources;readUrl;(URL);generated |
| p;Sources;socketStream;();generated |
| p;Sources;sourceToParameter;(InputStream[],List);generated |
| p;Sources;wrappedSocketStream;();generated |

View File

@@ -0,0 +1 @@
utils/model-generator/CaptureNegativeSummaryModels.ql

View File

@@ -14,6 +14,7 @@ class Generator:
self.generateSinks = False self.generateSinks = False
self.generateSources = False self.generateSources = False
self.generateSummaries = False self.generateSummaries = False
self.generateNegativeSummaries = False
self.dryRun = False self.dryRun = False
@@ -25,11 +26,13 @@ This generates summary, source and sink models for the code in the database.
The files will be placed in `{self.language}/ql/lib/semmle/code/{self.language}/frameworks/<outputQll>` where The files will be placed in `{self.language}/ql/lib/semmle/code/{self.language}/frameworks/<outputQll>` where
outputQll is the name (and path) of the output QLL file. Usually, models are grouped by their outputQll is the name (and path) of the output QLL file. Usually, models are grouped by their
respective frameworks. respective frameworks.
If negative summaries are produced a file prefixed with `Negative` will be generated and stored in the same folder.
Which models are generated is controlled by the flags: Which models are generated is controlled by the flags:
--with-sinks --with-sinks
--with-sources --with-sources
--with-summaries --with-summaries
--with-negative-summaries
If none of these flags are specified, all models are generated. If none of these flags are specified, all models are generated.
--dry-run: Only run the queries, but don't write to file. --dry-run: Only run the queries, but don't write to file.
@@ -46,12 +49,14 @@ Requirements: `codeql` should both appear on your path.
self.codeQlRoot = subprocess.check_output(["git", "rev-parse", "--show-toplevel"]).decode("utf-8").strip() self.codeQlRoot = subprocess.check_output(["git", "rev-parse", "--show-toplevel"]).decode("utf-8").strip()
if not target.endswith(".qll"): if not target.endswith(".qll"):
target += ".qll" target += ".qll"
self.filename = os.path.basename(target) filename = os.path.basename(target)
self.shortname = self.filename[:-4] dirname = os.path.dirname(target)
self.shortname = filename[:-4]
self.database = database self.database = database
self.generatedFrameworks = os.path.join( self.generatedFrameworks = os.path.join(
self.codeQlRoot, f"{self.language}/ql/lib/semmle/code/{self.language}/frameworks/") self.codeQlRoot, f"{self.language}/ql/lib/semmle/code/{self.language}/frameworks/")
self.frameworkTarget = os.path.join(self.generatedFrameworks, target) self.frameworkTarget = os.path.join(self.generatedFrameworks, dirname, filename)
self.negativeFrameworkTarget = os.path.join(self.generatedFrameworks, dirname, "Negative" + filename)
self.workDir = tempfile.mkdtemp() self.workDir = tempfile.mkdtemp()
os.makedirs(self.generatedFrameworks, exist_ok=True) os.makedirs(self.generatedFrameworks, exist_ok=True)
@@ -76,12 +81,16 @@ Requirements: `codeql` should both appear on your path.
sys.argv.remove("--with-summaries") sys.argv.remove("--with-summaries")
generator.generateSummaries = True generator.generateSummaries = True
if "--with-negative-summaries" in sys.argv:
sys.argv.remove("--with-negative-summaries")
generator.generateNegativeSummaries = True
if "--dry-run" in sys.argv: if "--dry-run" in sys.argv:
sys.argv.remove("--dry-run") sys.argv.remove("--dry-run")
generator.dryRun = True generator.dryRun = True
if not generator.generateSinks and not generator.generateSources and not generator.generateSummaries: if not generator.generateSinks and not generator.generateSources and not generator.generateSummaries and not generator.generateNegativeSummaries:
generator.generateSinks = generator.generateSources = generator.generateSummaries = True generator.generateSinks = generator.generateSources = generator.generateSummaries = generator.generateNegativeSummaries = True
if len(sys.argv) != 3: if len(sys.argv) != 3:
generator.printHelp() generator.printHelp()
@@ -181,26 +190,51 @@ private import semmle.code.{self.language}.dataflow.ExternalFlow
""" """
def makeNegativeContent(self):
if self.generateNegativeSummaries:
negativeSummaryRows = self.runQuery("negative summary models", "CaptureNegativeSummaryModels.ql")
negativeSummaryCsv = self.asCsvModel("NegativeSummaryModelCsv", "NegativeSummary", negativeSummaryRows)
else:
negativeSummaryCsv = ""
def save(self, content): return f"""
with open(self.frameworkTarget, "w") as frameworkQll: /**
frameworkQll.write(content) * THIS FILE IS AN AUTO-GENERATED MODELS AS DATA FILE. DO NOT EDIT.
* Definitions of negative summaries in the {self.shortname} framework.
*/
cmd = ['codeql', 'query', 'format', '--in-place', self.frameworkTarget] import {self.language}
private import semmle.code.{self.language}.dataflow.ExternalFlow
{negativeSummaryCsv}
"""
def save(self, content, target):
with open(target, "w") as targetQll:
targetQll.write(content)
cmd = ['codeql', 'query', 'format', '--in-place', target]
ret = subprocess.call(cmd) ret = subprocess.call(cmd)
if ret != 0: if ret != 0:
print("Failed to format query. Failed command was: " + shlex.join(cmd)) print("Failed to format query. Failed command was: " + shlex.join(cmd))
sys.exit(1) sys.exit(1)
print("") print("")
print("CSV model written to " + self.frameworkTarget) print("CSV model written to " + target)
def run(self): def run(self):
content = self.makeContent() content = self.makeContent()
negativeContent = self.makeNegativeContent()
if self.dryRun: if self.dryRun:
print("CSV Models generated, but not written to file.") print("CSV Models generated, but not written to file.")
sys.exit(0) sys.exit(0)
self.save(content) if self.generateSinks or self.generateSinks or self.generateSummaries:
self.save(content, self.frameworkTarget)
if self.generateNegativeSummaries:
self.save(negativeContent, self.negativeFrameworkTarget)

View File

@@ -63,7 +63,7 @@ class GlobalVariable extends Variable instanceof GlobalVariableImpl {
/** An instance variable. */ /** An instance variable. */
class InstanceVariable extends Variable instanceof InstanceVariableImpl { class InstanceVariable extends Variable instanceof InstanceVariableImpl {
/** Holds is this variable is a class instance variable. */ /** Holds if this variable is a class instance variable. */
final predicate isClassInstanceVariable() { super.isClassInstanceVariable() } final predicate isClassInstanceVariable() { super.isClassInstanceVariable() }
final override InstanceVariableAccess getAnAccess() { result.getVariable() = this } final override InstanceVariableAccess getAnAccess() { result.getVariable() = this }

View File

@@ -240,6 +240,16 @@ module Public {
*/ */
predicate isAutoGenerated() { none() } predicate isAutoGenerated() { none() }
} }
/** A callable with a flow summary stating there is no flow via the callable. */
class NegativeSummarizedCallable extends SummarizedCallableBase {
NegativeSummarizedCallable() { negativeSummaryElement(this, _) }
/**
* Holds if the negative summary is auto generated.
*/
predicate isAutoGenerated() { negativeSummaryElement(this, true) }
}
} }
/** /**
@@ -1094,7 +1104,7 @@ module Private {
/** Provides a query predicate for outputting a set of relevant flow summaries. */ /** Provides a query predicate for outputting a set of relevant flow summaries. */
module TestOutput { module TestOutput {
/** A flow summary to include in the `summary/3` query predicate. */ /** A flow summary to include in the `summary/1` query predicate. */
abstract class RelevantSummarizedCallable instanceof SummarizedCallable { abstract class RelevantSummarizedCallable instanceof SummarizedCallable {
/** Gets the string representation of this callable used by `summary/1`. */ /** Gets the string representation of this callable used by `summary/1`. */
abstract string getCallableCsv(); abstract string getCallableCsv();
@@ -1109,6 +1119,14 @@ module Private {
string toString() { result = super.toString() } string toString() { result = super.toString() }
} }
/** A flow summary to include in the `negativeSummary/1` query predicate. */
abstract class RelevantNegativeSummarizedCallable instanceof NegativeSummarizedCallable {
/** Gets the string representation of this callable used by `summary/1`. */
abstract string getCallableCsv();
string toString() { result = super.toString() }
}
/** Render the kind in the format used in flow summaries. */ /** Render the kind in the format used in flow summaries. */
private string renderKind(boolean preservesValue) { private string renderKind(boolean preservesValue) {
preservesValue = true and result = "value" preservesValue = true and result = "value"
@@ -1116,8 +1134,12 @@ module Private {
preservesValue = false and result = "taint" preservesValue = false and result = "taint"
} }
private string renderProvenance(RelevantSummarizedCallable c) { private string renderProvenance(SummarizedCallable c) {
if c.(SummarizedCallable).isAutoGenerated() then result = "generated" else result = "manual" if c.isAutoGenerated() then result = "generated" else result = "manual"
}
private string renderProvenanceNegative(NegativeSummarizedCallable c) {
if c.isAutoGenerated() then result = "generated" else result = "manual"
} }
/** /**
@@ -1132,8 +1154,23 @@ module Private {
| |
c.relevantSummary(input, output, preservesValue) and c.relevantSummary(input, output, preservesValue) and
csv = csv =
c.getCallableCsv() + getComponentStackCsv(input) + ";" + getComponentStackCsv(output) + c.getCallableCsv() // Callable information
";" + renderKind(preservesValue) + ";" + renderProvenance(c) + getComponentStackCsv(input) + ";" // input
+ getComponentStackCsv(output) + ";" // output
+ renderKind(preservesValue) + ";" // kind
+ renderProvenance(c) // provenance
)
}
/**
* Holds if a negative flow summary `csv` exists (semi-colon separated format). Used for testing purposes.
* The syntax is: "namespace;type;name;signature;provenance"",
*/
query predicate negativeSummary(string csv) {
exists(RelevantNegativeSummarizedCallable c |
csv =
c.getCallableCsv() // Callable information
+ renderProvenanceNegative(c) // provenance
) )
} }
} }

View File

@@ -59,6 +59,13 @@ predicate summaryElement(
) )
} }
/**
* Holds if a negative flow summary exists for `c`, which means that there is no
* flow through `c`. The flag `generated` states whether the summary is autogenerated.
* Note. Negative flow summaries has not been implemented for ruby.
*/
predicate negativeSummaryElement(FlowSummary::SummarizedCallable c, boolean generated) { none() }
bindingset[arg] bindingset[arg]
private SummaryComponent interpretElementArg(string arg) { private SummaryComponent interpretElementArg(string arg) {
arg = "?" and arg = "?" and

View File

@@ -111,18 +111,14 @@ class SummaryModelCsv extends Unit {
abstract predicate row(string row); abstract predicate row(string row);
} }
private predicate sourceModel(string row) { any(SourceModelCsv s).row(row) } /** Holds if `row` is a source model. */
predicate sourceModel(string row) { any(SourceModelCsv s).row(row) }
private predicate sinkModel(string row) { any(SinkModelCsv s).row(row) } /** Holds if `row` is a sink model. */
predicate sinkModel(string row) { any(SinkModelCsv s).row(row) }
private predicate summaryModel(string row) { any(SummaryModelCsv s).row(row) } /** Holds if `row` is a summary model. */
predicate summaryModel(string row) { any(SummaryModelCsv s).row(row) }
bindingset[input]
private predicate getKind(string input, string kind, boolean generated) {
input.splitAt(":", 0) = "generated" and kind = input.splitAt(":", 1) and generated = true
or
not input.matches("%:%") and kind = input and generated = false
}
/** Holds if a source model exists for the given parameters. */ /** Holds if a source model exists for the given parameters. */
predicate sourceModel( predicate sourceModel(
@@ -139,8 +135,9 @@ predicate sourceModel(
row.splitAt(";", 4) = signature and row.splitAt(";", 4) = signature and
row.splitAt(";", 5) = ext and row.splitAt(";", 5) = ext and
row.splitAt(";", 6) = output and row.splitAt(";", 6) = output and
exists(string k | row.splitAt(";", 7) = k and getKind(k, kind, generated)) row.splitAt(";", 7) = kind
) ) and
generated = false
} }
/** Holds if a sink model exists for the given parameters. */ /** Holds if a sink model exists for the given parameters. */
@@ -158,8 +155,9 @@ predicate sinkModel(
row.splitAt(";", 4) = signature and row.splitAt(";", 4) = signature and
row.splitAt(";", 5) = ext and row.splitAt(";", 5) = ext and
row.splitAt(";", 6) = input and row.splitAt(";", 6) = input and
exists(string k | row.splitAt(";", 7) = k and getKind(k, kind, generated)) row.splitAt(";", 7) = kind
) ) and
generated = false
} }
/** Holds if a summary model exists for the given parameters. */ /** Holds if a summary model exists for the given parameters. */
@@ -178,8 +176,9 @@ predicate summaryModel(
row.splitAt(";", 5) = ext and row.splitAt(";", 5) = ext and
row.splitAt(";", 6) = input and row.splitAt(";", 6) = input and
row.splitAt(";", 7) = output and row.splitAt(";", 7) = output and
exists(string k | row.splitAt(";", 8) = k and getKind(k, kind, generated)) row.splitAt(";", 8) = kind
) ) and
generated = false
} }
private predicate relevantNamespace(string namespace) { private predicate relevantNamespace(string namespace) {
@@ -238,31 +237,7 @@ predicate modelCoverage(string namespace, int namespaces, string kind, string pa
/** Provides a query predicate to check the CSV data for validation errors. */ /** Provides a query predicate to check the CSV data for validation errors. */
module CsvValidation { module CsvValidation {
/** Holds if some row in a CSV-based flow model appears to contain typos. */ private string getInvalidModelInput() {
query predicate invalidModelRow(string msg) {
exists(string pred, string namespace, string type, string name, string signature, string ext |
sourceModel(namespace, type, _, name, signature, ext, _, _, _) and pred = "source"
or
sinkModel(namespace, type, _, name, signature, ext, _, _, _) and pred = "sink"
or
summaryModel(namespace, type, _, name, signature, ext, _, _, _, _) and pred = "summary"
|
not namespace.regexpMatch("[a-zA-Z0-9_\\.]+") and
msg = "Dubious namespace \"" + namespace + "\" in " + pred + " model."
or
not type.regexpMatch("[a-zA-Z0-9_<>,\\+]+") and
msg = "Dubious type \"" + type + "\" in " + pred + " model."
or
not name.regexpMatch("[a-zA-Z0-9_<>,]*") and
msg = "Dubious member name \"" + name + "\" in " + pred + " model."
or
not signature.regexpMatch("|\\([a-zA-Z0-9_<>\\.\\+\\*,\\[\\]]*\\)") and
msg = "Dubious signature \"" + signature + "\" in " + pred + " model."
or
not ext.regexpMatch("|Attribute") and
msg = "Unrecognized extra API graph element \"" + ext + "\" in " + pred + " model."
)
or
exists(string pred, AccessPath input, string part | exists(string pred, AccessPath input, string part |
sinkModel(_, _, _, _, _, _, input, _, _) and pred = "sink" sinkModel(_, _, _, _, _, _, input, _, _) and pred = "sink"
or or
@@ -277,9 +252,11 @@ module CsvValidation {
part = input.getToken(_) and part = input.getToken(_) and
parseParam(part, _) parseParam(part, _)
) and ) and
msg = "Unrecognized input specification \"" + part + "\" in " + pred + " model." result = "Unrecognized input specification \"" + part + "\" in " + pred + " model."
) )
or }
private string getInvalidModelOutput() {
exists(string pred, string output, string part | exists(string pred, string output, string part |
sourceModel(_, _, _, _, _, _, output, _, _) and pred = "source" sourceModel(_, _, _, _, _, _, output, _, _) and pred = "source"
or or
@@ -288,9 +265,35 @@ module CsvValidation {
invalidSpecComponent(output, part) and invalidSpecComponent(output, part) and
not part = "" and not part = "" and
not (part = ["Argument", "Parameter"] and pred = "source") and not (part = ["Argument", "Parameter"] and pred = "source") and
msg = "Unrecognized output specification \"" + part + "\" in " + pred + " model." result = "Unrecognized output specification \"" + part + "\" in " + pred + " model."
) )
or }
private string getInvalidModelKind() {
exists(string row, string kind | summaryModel(row) |
kind = row.splitAt(";", 8) and
not kind = ["taint", "value"] and
result = "Invalid kind \"" + kind + "\" in summary model."
)
}
private string getInvalidModelSubtype() {
exists(string pred, string row |
sourceModel(row) and pred = "source"
or
sinkModel(row) and pred = "sink"
or
summaryModel(row) and pred = "summary"
|
exists(string b |
b = row.splitAt(";", 2) and
not b = ["true", "false"] and
result = "Invalid boolean \"" + b + "\" in " + pred + " model."
)
)
}
private string getInvalidModelColumnCount() {
exists(string pred, string row, int expect | exists(string pred, string row, int expect |
sourceModel(row) and expect = 8 and pred = "source" sourceModel(row) and expect = 8 and pred = "source"
or or
@@ -301,39 +304,46 @@ module CsvValidation {
exists(int cols | exists(int cols |
cols = 1 + max(int n | exists(row.splitAt(";", n))) and cols = 1 + max(int n | exists(row.splitAt(";", n))) and
cols != expect and cols != expect and
msg = result =
"Wrong number of columns in " + pred + " model row, expected " + expect + ", got " + cols + "Wrong number of columns in " + pred + " model row, expected " + expect + ", got " + cols +
"." "."
) )
)
}
private string getInvalidModelSignature() {
exists(string pred, string namespace, string type, string name, string signature, string ext |
sourceModel(namespace, type, _, name, signature, ext, _, _, _) and pred = "source"
or or
exists(string b | sinkModel(namespace, type, _, name, signature, ext, _, _, _) and pred = "sink"
b = row.splitAt(";", 2) and or
not b = ["true", "false"] and summaryModel(namespace, type, _, name, signature, ext, _, _, _, _) and pred = "summary"
msg = "Invalid boolean \"" + b + "\" in " + pred + " model." |
) not namespace.regexpMatch("[a-zA-Z0-9_\\.]+") and
) result = "Dubious namespace \"" + namespace + "\" in " + pred + " model."
or or
exists(string row, string k, string kind | summaryModel(row) | not type.regexpMatch("[a-zA-Z0-9_<>,\\+]+") and
k = row.splitAt(";", 8) and result = "Dubious type \"" + type + "\" in " + pred + " model."
getKind(k, kind, _) and or
not kind = ["taint", "value"] and not name.regexpMatch("[a-zA-Z0-9_<>,]*") and
msg = "Invalid kind \"" + kind + "\" in summary model." result = "Dubious member name \"" + name + "\" in " + pred + " model."
) or
or not signature.regexpMatch("|\\([a-zA-Z0-9_<>\\.\\+\\*,\\[\\]]*\\)") and
exists(string row, string k, string kind | sinkModel(row) | result = "Dubious signature \"" + signature + "\" in " + pred + " model."
k = row.splitAt(";", 7) and or
getKind(k, kind, _) and not ext.regexpMatch("|Attribute") and
not kind = ["code", "sql", "xss", "remote", "html"] and result = "Unrecognized extra API graph element \"" + ext + "\" in " + pred + " model."
msg = "Invalid kind \"" + kind + "\" in sink model."
)
or
exists(string row, string k, string kind | sourceModel(row) |
k = row.splitAt(";", 7) and
getKind(k, kind, _) and
not kind = "local" and
msg = "Invalid kind \"" + kind + "\" in source model."
) )
} }
/** Holds if some row in a CSV-based flow model appears to contain typos. */
query predicate invalidModelRow(string msg) {
msg =
[
getInvalidModelSignature(), getInvalidModelInput(), getInvalidModelOutput(),
getInvalidModelSubtype(), getInvalidModelColumnCount(), getInvalidModelKind()
]
}
} }
private predicate elementSpec( private predicate elementSpec(

View File

@@ -240,6 +240,16 @@ module Public {
*/ */
predicate isAutoGenerated() { none() } predicate isAutoGenerated() { none() }
} }
/** A callable with a flow summary stating there is no flow via the callable. */
class NegativeSummarizedCallable extends SummarizedCallableBase {
NegativeSummarizedCallable() { negativeSummaryElement(this, _) }
/**
* Holds if the negative summary is auto generated.
*/
predicate isAutoGenerated() { negativeSummaryElement(this, true) }
}
} }
/** /**
@@ -1094,7 +1104,7 @@ module Private {
/** Provides a query predicate for outputting a set of relevant flow summaries. */ /** Provides a query predicate for outputting a set of relevant flow summaries. */
module TestOutput { module TestOutput {
/** A flow summary to include in the `summary/3` query predicate. */ /** A flow summary to include in the `summary/1` query predicate. */
abstract class RelevantSummarizedCallable instanceof SummarizedCallable { abstract class RelevantSummarizedCallable instanceof SummarizedCallable {
/** Gets the string representation of this callable used by `summary/1`. */ /** Gets the string representation of this callable used by `summary/1`. */
abstract string getCallableCsv(); abstract string getCallableCsv();
@@ -1109,6 +1119,14 @@ module Private {
string toString() { result = super.toString() } string toString() { result = super.toString() }
} }
/** A flow summary to include in the `negativeSummary/1` query predicate. */
abstract class RelevantNegativeSummarizedCallable instanceof NegativeSummarizedCallable {
/** Gets the string representation of this callable used by `summary/1`. */
abstract string getCallableCsv();
string toString() { result = super.toString() }
}
/** Render the kind in the format used in flow summaries. */ /** Render the kind in the format used in flow summaries. */
private string renderKind(boolean preservesValue) { private string renderKind(boolean preservesValue) {
preservesValue = true and result = "value" preservesValue = true and result = "value"
@@ -1116,8 +1134,12 @@ module Private {
preservesValue = false and result = "taint" preservesValue = false and result = "taint"
} }
private string renderProvenance(RelevantSummarizedCallable c) { private string renderProvenance(SummarizedCallable c) {
if c.(SummarizedCallable).isAutoGenerated() then result = "generated" else result = "manual" if c.isAutoGenerated() then result = "generated" else result = "manual"
}
private string renderProvenanceNegative(NegativeSummarizedCallable c) {
if c.isAutoGenerated() then result = "generated" else result = "manual"
} }
/** /**
@@ -1132,8 +1154,23 @@ module Private {
| |
c.relevantSummary(input, output, preservesValue) and c.relevantSummary(input, output, preservesValue) and
csv = csv =
c.getCallableCsv() + getComponentStackCsv(input) + ";" + getComponentStackCsv(output) + c.getCallableCsv() // Callable information
";" + renderKind(preservesValue) + ";" + renderProvenance(c) + getComponentStackCsv(input) + ";" // input
+ getComponentStackCsv(output) + ";" // output
+ renderKind(preservesValue) + ";" // kind
+ renderProvenance(c) // provenance
)
}
/**
* Holds if a negative flow summary `csv` exists (semi-colon separated format). Used for testing purposes.
* The syntax is: "namespace;type;name;signature;provenance"",
*/
query predicate negativeSummary(string csv) {
exists(RelevantNegativeSummarizedCallable c |
csv =
c.getCallableCsv() // Callable information
+ renderProvenanceNegative(c) // provenance
) )
} }
} }

View File

@@ -67,6 +67,13 @@ predicate summaryElement(
) )
} }
/**
* Holds if a negative flow summary exists for `c`, which means that there is no
* flow through `c`. The flag `generated` states whether the summary is autogenerated.
* Note. Negative flow summaries has not been implemented for swift.
*/
predicate negativeSummaryElement(AbstractFunctionDecl c, boolean generated) { none() }
/** /**
* Holds if an external source specification exists for `e` with output specification * Holds if an external source specification exists for `e` with output specification
* `output`, kind `kind`, and a flag `generated` stating whether the source specification is * `output`, kind `kind`, and a flag `generated` stating whether the source specification is