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:
* - Sources:
* `namespace; type; subtypes; name; signature; ext; output; kind`
* `namespace; type; subtypes; name; signature; ext; output; kind; provenance`
* - Sinks:
* `namespace; type; subtypes; name; signature; ext; input; kind`
* `namespace; type; subtypes; name; signature; ext; input; kind; provenance`
* - 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
* reading.
* 1. The `namespace` column selects a namespace.
@@ -163,11 +164,27 @@ class SummaryModelCsv extends Unit {
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. */
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) {
sourceModel(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. */
module CsvValidation {
/** Holds if some row in a CSV-based flow model appears to contain typos. */
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
private string getInvalidModelInput() {
exists(string pred, AccessPath input, string part |
sinkModel(_, _, _, _, _, _, input, _, _) and pred = "sink"
or
@@ -332,9 +332,11 @@ module CsvValidation {
part = input.getToken(_) and
parseParam(part, _)
) 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 |
sourceModel(_, _, _, _, _, _, output, _, _) and pred = "source"
or
@@ -343,58 +345,123 @@ module CsvValidation {
invalidSpecComponent(output, part) and
not part = "" 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"
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
}
private string getInvalidModelKind() {
exists(string row, string kind | summaryModel(row) |
kind = row.splitAt(";", 8) and
not kind = ["taint", "value"] and
msg = "Invalid kind \"" + kind + "\" in summary model."
result = "Invalid kind \"" + kind + "\" in summary model."
)
or
exists(string row, string kind | sinkModel(row) |
kind = row.splitAt(";", 7) and
not kind = ["code", "sql", "xss", "remote", "html"] and
not kind.matches("encryption-%") and
msg = "Invalid kind \"" + kind + "\" in sink model."
result = "Invalid kind \"" + kind + "\" in sink model."
)
or
exists(string row, string kind | sourceModel(row) |
kind = row.splitAt(";", 7) 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(
string namespace, string type, boolean subtypes, string name, string signature, string ext
) {
sourceModel(namespace, type, subtypes, name, signature, ext, _, _, _) or
sinkModel(namespace, type, subtypes, name, signature, ext, _, _, _) or
sourceModel(namespace, type, subtypes, name, signature, ext, _, _, _)
or
sinkModel(namespace, type, subtypes, name, signature, ext, _, _, _)
or
summaryModel(namespace, type, subtypes, name, signature, ext, _, _, _, _)
or
negativeSummaryModel(namespace, type, name, signature, _) and ext = "" and subtypes = false
}
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(
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
* point it is still live, without crossing another SSA definition of the
* same source variable.

View File

@@ -2129,18 +2129,37 @@ module Csv {
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) {
exists(string namespace, string type, string name |
c.getDeclaringType().hasQualifiedName(namespace, type) and
c.hasQualifiedName(_, name) and
exists(string namespace, string type, string name, string parameters |
partialModel(c, namespace, type, name, parameters) and
result =
namespace + ";" //
+ type + ";" //
+ getCallableOverride(c) + ";" //
+ name + ";" //
+ "(" + parameterQualifiedTypeNamesToString(c) + ")" + ";" //
+ parameters + ";" //
+ /* 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() }
}
/** 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. */
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 {
/** Gets the string representation of this callable used by `summary/1`. */
abstract string getCallableCsv();
@@ -1109,6 +1119,14 @@ module Private {
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. */
private string renderKind(boolean preservesValue) {
preservesValue = true and result = "value"
@@ -1116,8 +1134,12 @@ module Private {
preservesValue = false and result = "taint"
}
private string renderProvenance(RelevantSummarizedCallable c) {
if c.(SummarizedCallable).isAutoGenerated() then result = "generated" else result = "manual"
private string renderProvenance(SummarizedCallable c) {
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
csv =
c.getCallableCsv() + getComponentStackCsv(input) + ";" + getComponentStackCsv(output) +
";" + renderKind(preservesValue) + ";" + renderProvenance(c)
c.getCallableCsv() // Callable information
+ 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
* `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 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 predicate getRelevantUsages(ExternalApi api, int usages) {
not api.isUninteresting() and
not api.isSupported() and
not api instanceof FlowSummaryImpl::Public::NegativeSummarizedCallable and
usages = strictcount(DispatchCall c | c = api.getACall())
}

View File

@@ -6,7 +6,7 @@
private import semmle.code.csharp.dataflow.ExternalFlow
private import internal.CaptureModels
private import internal.CaptureFlow
private import internal.CaptureSummaryFlow
from TargetApi api, string flow
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 internal.CaptureModels
private import internal.CaptureFlow
private import internal.CaptureSummaryFlow
from TargetApi api, string flow
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"
}
string asNegativeSummaryModel(TargetApi api) { result = asPartialNegativeModel(api) + "generated" }
/**
* 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
not api instanceof CS::ConversionOperator 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 asPartialNegativeModel = DataFlowPrivate::Csv::asPartialNegativeModel/1;
/**
* Holds for type `t` for fields that are relevant as an intermediate
* read or write step in the data flow analysis.

View File

@@ -79,3 +79,12 @@ 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

@@ -3,8 +3,8 @@
*/
import csharp
import semmle.code.csharp.dataflow.ExternalFlow
import DataFlow::PathGraph
import semmle.code.csharp.dataflow.ExternalFlow
import CsvValidation
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 DataFlow
import semmle.code.csharp.dataflow.ExternalFlow
import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
import CsvValidation
import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
class SinkModelTest extends SinkModelCsv {
override predicate row(string row) {

View File

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

View File

@@ -1,10 +1,10 @@
import csharp
import DataFlow
import semmle.code.csharp.dataflow.ExternalFlow
import CsvValidation
import semmle.code.csharp.dataflow.FlowSummary
import semmle.code.csharp.dataflow.internal.DataFlowDispatch as DataFlowDispatch
import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
import CsvValidation
private class SummaryModelTest extends SummaryModelCsv {
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
private class IncludeAllSummarizedCallable extends IncludeSummarizedCallable {
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[3].Element;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;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 |
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;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 |
negativeSummary
sourceNode
sinkNode
| 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.internal.FlowSummaryImpl::Private::TestOutput
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate
import semmle.code.csharp.dataflow.internal.NegativeSummary
abstract class IncludeSummarizedCallable extends RelevantSummarizedCallable {
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

@@ -98,4 +98,16 @@ public class HigherOrderParameters
{
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
{
// No flow summary and no negative summary either.
~BasicFlow() { }
private string tainted;
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.
*
@@ -121,20 +115,14 @@ class SummaryModelCsv extends Unit {
abstract predicate row(string row);
}
private predicate sourceModel(string row) {
sourceModelCsv(row) or
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) {
sinkModelCsv(row) or
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) {
summaryModelCsv(row) or
any(SummaryModelCsv s).row(row)
}
/** Holds if `row` is a summary model. */
predicate summaryModel(string row) { any(SummaryModelCsv s).row(row) }
/** Holds if a source model exists for the given parameters. */
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. */
module CsvValidation {
/** Holds if some row in a CSV-based flow model appears to contain typos. */
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
private string getInvalidModelInput() {
exists(string pred, AccessPath input, string part |
sinkModel(_, _, _, _, _, _, input, _) and pred = "sink"
or
@@ -309,9 +273,11 @@ module CsvValidation {
part = input.getToken(_) and
parseParam(part, _)
) 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 |
sourceModel(_, _, _, _, _, _, output, _) and pred = "source"
or
@@ -320,9 +286,35 @@ module CsvValidation {
invalidSpecComponent(output, part) and
not part = "" 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 |
sourceModel(row) and expect = 8 and pred = "source"
or
@@ -333,18 +325,46 @@ module CsvValidation {
exists(int cols |
cols = 1 + max(int n | exists(row.splitAt(";", n))) and
cols != expect and
msg =
result =
"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]

View File

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

View File

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

View File

@@ -5,11 +5,13 @@
*
* The CSV specification has the following columns:
* - Sources:
* `namespace; type; subtypes; name; signature; ext; output; kind`
* `namespace; type; subtypes; name; signature; ext; output; kind; provenance`
* - Sinks:
* `namespace; type; subtypes; name; signature; ext; input; kind`
* `namespace; type; subtypes; name; signature; ext; input; kind; provenance`
* - 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
* reading.
@@ -152,234 +154,6 @@ private module Frameworks {
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.
*
@@ -410,21 +184,262 @@ class SummaryModelCsv extends Unit {
abstract predicate row(string row);
}
private predicate sourceModel(string row) {
sourceModelCsv(row) or
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) {
sinkModelCsv(row) or
any(SinkModelCsv s).row(row)
private class SourceModelCsvBase extends SourceModelCsv {
override predicate row(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 summaryModel(string row) {
summaryModelCsv(row) or
any(SummaryModelCsv s).row(row)
private class SinkModelCsvBase extends SinkModelCsv {
override predicate row(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 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. */
predicate sourceModel(
string namespace, string type, boolean subtypes, string name, string signature, string ext,
@@ -492,6 +507,20 @@ predicate summaryModel(
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) {
sourceModel(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. */
module CsvValidation {
/** Holds if some row in a CSV-based flow model appears to contain typos. */
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
private string getInvalidModelInput() {
exists(string pred, string input, string part |
sinkModel(_, _, _, _, _, _, input, _, _) and pred = "sink"
or
@@ -594,9 +592,11 @@ module CsvValidation {
part = input.(AccessPath).getToken(0) and
parseParam(part, _)
) 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 |
sourceModel(_, _, _, _, _, _, output, _, _) and pred = "source"
or
@@ -605,35 +605,15 @@ module CsvValidation {
invalidSpecComponent(output, part) and
not part = "" 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"
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
}
private string getInvalidModelKind() {
exists(string row, string kind | summaryModel(row) |
kind = row.splitAt(";", 8) and
not kind = ["taint", "value"] and
msg = "Invalid kind \"" + kind + "\" in summary model."
result = "Invalid kind \"" + kind + "\" in summary model."
)
or
exists(string row, string kind | sinkModel(row) |
@@ -647,25 +627,110 @@ module CsvValidation {
] and
not kind.matches("regex-use%") and
not kind.matches("qltest%") and
msg = "Invalid kind \"" + kind + "\" in sink model."
result = "Invalid kind \"" + kind + "\" in sink model."
)
or
exists(string row, string kind | sourceModel(row) |
kind = row.splitAt(";", 7) and
not kind = ["remote", "contentprovider", "android-widget", "android-external-storage-dir"] 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]
private predicate elementSpec(
string namespace, string type, boolean subtypes, string name, string signature, string ext
) {
sourceModel(namespace, type, subtypes, name, signature, ext, _, _, _) or
sinkModel(namespace, type, subtypes, name, signature, ext, _, _, _) or
sourceModel(namespace, type, subtypes, name, signature, ext, _, _, _)
or
sinkModel(namespace, type, subtypes, name, signature, ext, _, _, _)
or
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) {
@@ -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(
string namespace, string type, boolean subtypes, string name, string signature, string ext
) {

View File

@@ -240,6 +240,16 @@ module Public {
*/
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. */
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 {
/** Gets the string representation of this callable used by `summary/1`. */
abstract string getCallableCsv();
@@ -1109,6 +1119,14 @@ module Private {
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. */
private string renderKind(boolean preservesValue) {
preservesValue = true and result = "value"
@@ -1116,8 +1134,12 @@ module Private {
preservesValue = false and result = "taint"
}
private string renderProvenance(RelevantSummarizedCallable c) {
if c.(SummarizedCallable).isAutoGenerated() then result = "generated" else result = "manual"
private string renderProvenance(SummarizedCallable c) {
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
csv =
c.getCallableCsv() + getComponentStackCsv(input) + ";" + getComponentStackCsv(output) +
";" + renderKind(preservesValue) + ";" + renderProvenance(c)
c.getCallableCsv() // Callable information
+ 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. */
bindingset[c]
SummaryComponent interpretComponentSpecific(AccessPathToken c) {

View File

@@ -7,11 +7,13 @@
*/
import java
import semmle.code.java.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
import ExternalApi
private predicate getRelevantUsages(ExternalApi api, int usages) {
not api.isUninteresting() and
not api.isSupported() and
not api instanceof FlowSummaryImpl::Public::NegativeSummarizedCallable and
usages =
strictcount(Call c |
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
/**
* 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)
}
private import internal.CaptureSummaryFlow
from TargetApi api, string flow
where flow = captureFlow(api)

View File

@@ -48,6 +48,8 @@ private string asSummaryModel(TargetApi api, string input, string output, string
+ "generated"
}
string asNegativeSummaryModel(TargetApi api) { result = asPartialNegativeModel(api) + "generated" }
/**
* 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))
}
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.
*/
string asPartialModel(TargetApiSpecific api) {
result =
typeAsSummaryModel(api) + ";" //
+ isExtensible(bestTypeForModel(api)) + ";" //
+ api.getName() + ";" //
+ ExternalFlow::paramsString(api) + ";" //
+ /* ext + */ ";" //
exists(string type, string name, string parameters |
partialModel(api, type, name, parameters) and
result =
type + ";" //
+ isExtensible(bestTypeForModel(api)) + ";" //
+ 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) {

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 semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.ExternalFlow
import semmle.code.java.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
import CsvValidation
import semmle.code.java.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
class SummaryModelTest extends SummaryModelCsv {
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.generateSources = False
self.generateSummaries = False
self.generateNegativeSummaries = 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
outputQll is the name (and path) of the output QLL file. Usually, models are grouped by their
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:
--with-sinks
--with-sources
--with-summaries
--with-negative-summaries
If none of these flags are specified, all models are generated.
--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()
if not target.endswith(".qll"):
target += ".qll"
self.filename = os.path.basename(target)
self.shortname = self.filename[:-4]
filename = os.path.basename(target)
dirname = os.path.dirname(target)
self.shortname = filename[:-4]
self.database = database
self.generatedFrameworks = os.path.join(
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()
os.makedirs(self.generatedFrameworks, exist_ok=True)
@@ -76,12 +81,16 @@ Requirements: `codeql` should both appear on your path.
sys.argv.remove("--with-summaries")
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:
sys.argv.remove("--dry-run")
generator.dryRun = True
if not generator.generateSinks and not generator.generateSources and not generator.generateSummaries:
generator.generateSinks = generator.generateSources = generator.generateSummaries = True
if not generator.generateSinks and not generator.generateSources and not generator.generateSummaries and not generator.generateNegativeSummaries:
generator.generateSinks = generator.generateSources = generator.generateSummaries = generator.generateNegativeSummaries = True
if len(sys.argv) != 3:
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):
with open(self.frameworkTarget, "w") as frameworkQll:
frameworkQll.write(content)
return f"""
/**
* 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)
if ret != 0:
print("Failed to format query. Failed command was: " + shlex.join(cmd))
sys.exit(1)
print("")
print("CSV model written to " + self.frameworkTarget)
print("CSV model written to " + target)
def run(self):
content = self.makeContent()
negativeContent = self.makeNegativeContent()
if self.dryRun:
print("CSV Models generated, but not written to file.")
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. */
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 override InstanceVariableAccess getAnAccess() { result.getVariable() = this }

View File

@@ -240,6 +240,16 @@ module Public {
*/
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. */
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 {
/** Gets the string representation of this callable used by `summary/1`. */
abstract string getCallableCsv();
@@ -1109,6 +1119,14 @@ module Private {
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. */
private string renderKind(boolean preservesValue) {
preservesValue = true and result = "value"
@@ -1116,8 +1134,12 @@ module Private {
preservesValue = false and result = "taint"
}
private string renderProvenance(RelevantSummarizedCallable c) {
if c.(SummarizedCallable).isAutoGenerated() then result = "generated" else result = "manual"
private string renderProvenance(SummarizedCallable c) {
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
csv =
c.getCallableCsv() + getComponentStackCsv(input) + ";" + getComponentStackCsv(output) +
";" + renderKind(preservesValue) + ";" + renderProvenance(c)
c.getCallableCsv() // Callable information
+ 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]
private SummaryComponent interpretElementArg(string arg) {
arg = "?" and

View File

@@ -111,18 +111,14 @@ class SummaryModelCsv extends Unit {
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) }
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 `row` is a summary model. */
predicate summaryModel(string row) { any(SummaryModelCsv s).row(row) }
/** Holds if a source model exists for the given parameters. */
predicate sourceModel(
@@ -139,8 +135,9 @@ predicate sourceModel(
row.splitAt(";", 4) = signature and
row.splitAt(";", 5) = ext 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. */
@@ -158,8 +155,9 @@ predicate sinkModel(
row.splitAt(";", 4) = signature and
row.splitAt(";", 5) = ext 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. */
@@ -178,8 +176,9 @@ predicate summaryModel(
row.splitAt(";", 5) = ext and
row.splitAt(";", 6) = input 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) {
@@ -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. */
module CsvValidation {
/** Holds if some row in a CSV-based flow model appears to contain typos. */
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
private string getInvalidModelInput() {
exists(string pred, AccessPath input, string part |
sinkModel(_, _, _, _, _, _, input, _, _) and pred = "sink"
or
@@ -277,9 +252,11 @@ module CsvValidation {
part = input.getToken(_) and
parseParam(part, _)
) 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 |
sourceModel(_, _, _, _, _, _, output, _, _) and pred = "source"
or
@@ -288,9 +265,35 @@ module CsvValidation {
invalidSpecComponent(output, part) and
not part = "" 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 |
sourceModel(row) and expect = 8 and pred = "source"
or
@@ -301,39 +304,46 @@ module CsvValidation {
exists(int cols |
cols = 1 + max(int n | exists(row.splitAt(";", n))) and
cols != expect and
msg =
result =
"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
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 k, string kind | summaryModel(row) |
k = row.splitAt(";", 8) and
getKind(k, kind, _) and
not kind = ["taint", "value"] and
msg = "Invalid kind \"" + kind + "\" in summary model."
)
or
exists(string row, string k, string kind | sinkModel(row) |
k = row.splitAt(";", 7) and
getKind(k, kind, _) and
not kind = ["code", "sql", "xss", "remote", "html"] and
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."
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 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."
)
}
/** 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(

View File

@@ -240,6 +240,16 @@ module Public {
*/
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. */
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 {
/** Gets the string representation of this callable used by `summary/1`. */
abstract string getCallableCsv();
@@ -1109,6 +1119,14 @@ module Private {
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. */
private string renderKind(boolean preservesValue) {
preservesValue = true and result = "value"
@@ -1116,8 +1134,12 @@ module Private {
preservesValue = false and result = "taint"
}
private string renderProvenance(RelevantSummarizedCallable c) {
if c.(SummarizedCallable).isAutoGenerated() then result = "generated" else result = "manual"
private string renderProvenance(SummarizedCallable c) {
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
csv =
c.getCallableCsv() + getComponentStackCsv(input) + ";" + getComponentStackCsv(output) +
";" + renderKind(preservesValue) + ";" + renderProvenance(c)
c.getCallableCsv() // Callable information
+ 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
* `output`, kind `kind`, and a flag `generated` stating whether the source specification is