JavaScript: Move flow summaries to experimental.

Also update description and change note to call out their experimental character more clearly.
This commit is contained in:
Max Schaefer
2020-03-09 12:57:20 +00:00
parent 5a1bf94994
commit 3c785ecaa7
17 changed files with 15 additions and 9 deletions

View File

@@ -1,9 +0,0 @@
/**
* Imports the standard library and any taint-tracking configuration classes for which
* flow summarization should be enabled.
*
* To enable flow summarization for other queries, import their configuration classes here.
*/
import javascript
import semmle.javascript.security.dataflow.CodeInjection

View File

@@ -1,35 +0,0 @@
/**
* @name Extract flow step summaries
* @description Extracts flow step summaries, that is, tuples `(p1, lbl1, p2, lbl2, cfg)`
* representing the fact that data with flow label `lbl1` may flow from a
* user-controlled exit node of portal `p1` to an escaping entry node of portal `p2`,
* and have label `lbl2` at that point. Moreover, the path from `p1` to `p2` contains
* no sanitizers specified by configuration `cfg`.
* @kind flow-step-summary
* @id js/step-summary-extraction
*/
import Configurations
import PortalExitSource
import PortalEntrySink
from
TaintTracking::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink,
Portal p1, Portal p2, DataFlow::FlowLabel lbl1, DataFlow::FlowLabel lbl2,
DataFlow::MidPathNode last
where
cfg = source.getConfiguration() and
last = source.getASuccessor*() and
sink = last.getASuccessor() and
p1 = source.getNode().(PortalExitSource).getPortal() and
p2 = sink.getNode().(PortalEntrySink).getPortal() and
lbl1 = last.getPathSummary().getStartLabel() and
lbl2 = last.getPathSummary().getEndLabel() and
// avoid constructing infeasible paths
last.getPathSummary().hasCall() = false and
last.getPathSummary().hasReturn() = false and
// restrict to steps flow function parameters to returns
p1.(ParameterPortal).getBasePortal() = p2.(ReturnPortal).getBasePortal() and
// restrict to data/taint flow
lbl1 instanceof DataFlow::StandardFlowLabel
select p1.toString(), lbl1.toString(), p2.toString(), lbl2.toString(), cfg.toString()

View File

@@ -1,24 +0,0 @@
/**
* @name Extract sink summaries
* @description Extracts sink summaries, that is, tuples `(p, lbl, cfg)` representing the fact
* that data with flow label `lbl` may flow from a user-controlled exit node of portal
* `p` to a known sink for configuration `cfg`.
* @kind sink-summary
* @id js/sink-summary-extraction
*/
import Configurations
import PortalExitSource
import SinkFromAnnotation
from
DataFlow::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink,
Portal p, DataFlow::MidPathNode last
where
cfg = source.getConfiguration() and
last = source.getASuccessor*() and
sink = last.getASuccessor() and
p = source.getNode().(PortalExitSource).getPortal() and
// avoid constructing infeasible paths
last.getPathSummary().hasReturn() = false
select p.toString(), last.getPathSummary().getStartLabel().toString(), cfg.toString()

View File

@@ -1,24 +0,0 @@
/**
* @name Extract source summaries
* @description Extracts source summaries, that is, tuples `(p, lbl, cfg)` representing the fact
* that data may flow from a known source for configuration `cfg` to an escaping entry
* node of portal `p`, and have flow label `lbl` at that point.
* @kind source-summary
* @id js/source-summary-extraction
*/
import Configurations
import PortalEntrySink
import SourceFromAnnotation
from
DataFlow::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink,
Portal p, DataFlow::MidPathNode last
where
cfg = source.getConfiguration() and
last = source.getASuccessor*() and
sink = last.getASuccessor() and
p = sink.getNode().(PortalEntrySink).getPortal() and
// avoid constructing infeasible paths
last.getPathSummary().hasCall() = false
select p.toString(), last.getPathSummary().getEndLabel().toString(), cfg.toString()

View File

@@ -1,157 +0,0 @@
/**
* Provides classes for importing source, sink and flow step summaries
* from external CSV data added at snapshot build time.
*/
import javascript
import semmle.javascript.dataflow.Portals
import external.ExternalArtifact
private import Shared
/**
* An additional source specified in an `additional-sources.csv` file.
*/
class AdditionalSourceSpec extends ExternalData {
AdditionalSourceSpec() { this.getDataPath() = "additional-sources.csv" }
/**
* Gets the portal of this additional source.
*/
Portal getPortal() { result.toString() = getField(0) }
/**
* Gets the flow label of this source.
*/
DataFlow::FlowLabel getFlowLabel() { sourceFlowLabelSpec(result, getField(1)) }
/**
* Gets the configuration for which this is a source, or any
* configuration if this source does not specify a configuration.
*/
DataFlow::Configuration getConfiguration() { configSpec(result, getField(2)) }
override string toString() {
exists(string config |
if getField(2) = "" then config = "any configuration" else config = getConfiguration()
|
result = getPortal() + " as " + getFlowLabel() + " source for " + config
)
}
}
private class AdditionalSourceFromSpec extends DataFlow::AdditionalSource {
AdditionalSourceSpec spec;
AdditionalSourceFromSpec() { this = spec.getPortal().getAnExitNode(_) }
override predicate isSourceFor(DataFlow::Configuration cfg, DataFlow::FlowLabel lbl) {
cfg = spec.getConfiguration() and lbl = spec.getFlowLabel()
}
}
/**
* An additional sink specified in an `additional-sinks.csv` file.
*/
class AdditionalSinkSpec extends ExternalData {
AdditionalSinkSpec() { this.getDataPath() = "additional-sinks.csv" }
/**
* Gets the portal specification of this additional sink.
*/
Portal getPortal() { result.toString() = getField(0) }
/**
* Gets the flow label of this sink, or any standard flow label if this sink
* does not specify a flow label.
*/
DataFlow::FlowLabel getFlowLabel() { sinkFlowLabelSpec(result, getField(1)) }
/**
* Gets the configuration for which this is a sink, or any configuration if
* this sink does not specify a configuration.
*/
DataFlow::Configuration getConfiguration() { configSpec(result, getField(2)) }
override string toString() {
exists(string labels, string config |
labels = strictconcat(getFlowLabel(), " or ") and
if getField(2) = "" then config = "any configuration" else config = getConfiguration()
|
result = getPortal() + " as " + labels + " sink for " + config
)
}
}
private class AdditionalSinkFromSpec extends DataFlow::AdditionalSink {
AdditionalSinkSpec spec;
AdditionalSinkFromSpec() { this = spec.getPortal().getAnEntryNode(_) }
override predicate isSinkFor(DataFlow::Configuration cfg, DataFlow::FlowLabel lbl) {
cfg = spec.getConfiguration() and lbl = spec.getFlowLabel()
}
}
/**
* An additional flow step specified in an `additional-steps.csv` file.
*/
class AdditionalStepSpec extends ExternalData {
AdditionalStepSpec() { this.getDataPath() = "additional-steps.csv" }
/**
* Gets the start portal of this additional step.
*/
Portal getStartPortal() { result.toString() = getField(0) }
/**
* Gets the start flow label of this additional step.
*/
DataFlow::FlowLabel getStartFlowLabel() { result.toString() = getField(1) }
/**
* Gets the end portal of this additional step.
*/
Portal getEndPortal() { result.toString() = getField(2) }
/**
* Gets the end flow label of this additional step.
*/
DataFlow::FlowLabel getEndFlowLabel() { result.toString() = getField(3) }
/**
* Gets the configuration to which this step should be added.
*/
DataFlow::Configuration getConfiguration() { configSpec(result, getField(4)) }
override string toString() {
exists(string config |
if getField(4) = "" then config = "any configuration" else config = getConfiguration()
|
result =
"edge from " + getStartPortal() + " to " + getEndPortal() + ", transforming " +
getStartFlowLabel() + " into " + getEndFlowLabel() + " for " + config
)
}
}
private class AdditionalFlowStepFromSpec extends DataFlow::Configuration {
AdditionalStepSpec spec;
DataFlow::Node entry;
DataFlow::Node exit;
AdditionalFlowStepFromSpec() {
this = spec.getConfiguration() and
entry = spec.getStartPortal().getAnEntryNode(_) and
exit = spec.getEndPortal().getAnExitNode(_)
}
override predicate isAdditionalFlowStep(
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel predlbl,
DataFlow::FlowLabel succlbl
) {
pred = entry and
succ = exit and
predlbl = spec.getStartFlowLabel() and
succlbl = spec.getEndFlowLabel()
}
}

View File

@@ -1,98 +0,0 @@
/**
* Provides classes for importing source, sink and flow step summaries
* through external predicates.
*/
import javascript
import semmle.javascript.dataflow.Portals
import external.ExternalArtifact
import Shared
/**
* An external predicate providing information about additional sources.
*
* This predicate can be populated from the output of the `ExtractSourceSummaries` query.
*/
external predicate additionalSources(string portal, string flowLabel, string config);
/**
* An external predicate providing information about additional sinks.
*
* This predicate can be populated from the output of the `ExtractSinkSummaries` query.
*/
external predicate additionalSinks(string portal, string flowLabel, string config);
/**
* An external predicate providing information about additional flow steps.
*
* This predicate can be populated from the output of the `ExtractFlowStepSummaries` query.
*/
external predicate additionalSteps(
string startPortal, string startFlowLabel, string endPortal, string endFlowLabel, string config
);
/**
* An additional source specified through the `additionalSources` predicate.
*/
private class AdditionalSourceFromSpec extends DataFlow::AdditionalSource {
Portal portal;
string flowLabel;
string config;
AdditionalSourceFromSpec() {
additionalSources(portal.toString(), flowLabel, config) and
this = portal.getAnExitNode(_)
}
override predicate isSourceFor(DataFlow::Configuration cfg, DataFlow::FlowLabel lbl) {
configSpec(cfg, config) and sourceFlowLabelSpec(lbl, flowLabel)
}
}
/**
* An additional sink specified through the `additionalSinks` predicate.
*/
private class AdditionalSinkFromSpec extends DataFlow::AdditionalSink {
Portal portal;
string flowLabel;
string config;
AdditionalSinkFromSpec() {
additionalSinks(portal.toString(), flowLabel, config) and
this = portal.getAnEntryNode(_)
}
override predicate isSinkFor(DataFlow::Configuration cfg, DataFlow::FlowLabel lbl) {
configSpec(cfg, config) and sinkFlowLabelSpec(lbl, flowLabel)
}
}
/**
* An additional flow step specified through the `additionalSteps` predicate.
*/
private class AdditionalFlowStepFromSpec extends DataFlow::Configuration {
DataFlow::Node entry;
string startFlowLabel;
DataFlow::Node exit;
string endFlowLabel;
AdditionalFlowStepFromSpec() {
exists(Portal startPortal, Portal endPortal, string config |
additionalSteps(startPortal.toString(), startFlowLabel, endPortal.toString(), endFlowLabel,
config) and
configSpec(this, config) and
entry = startPortal.getAnEntryNode(_) and
exit = endPortal.getAnExitNode(_)
)
}
override predicate isAdditionalFlowStep(
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel predlbl,
DataFlow::FlowLabel succlbl
) {
pred = entry and
succ = exit and
predlbl = startFlowLabel and
succlbl = endFlowLabel
}
}

View File

@@ -1,17 +0,0 @@
import javascript
import semmle.javascript.dataflow.Portals
/**
* An escaping entry node of a portal, viewed as an additional sink node for any flow
* configuration currently in scope.
*/
class PortalEntrySink extends DataFlow::AdditionalSink {
Portal p;
PortalEntrySink() { this = p.getAnEntryNode(true) }
override predicate isSinkFor(DataFlow::Configuration cfg, DataFlow::FlowLabel lbl) { any() }
/** Gets the portal of which this is an entry node. */
Portal getPortal() { result = p }
}

View File

@@ -1,17 +0,0 @@
import javascript
import semmle.javascript.dataflow.Portals
/**
* A remote exit node of a portal, viewed as an additional source node for any flow
* configuration currently in scope.
*/
class PortalExitSource extends DataFlow::AdditionalSource {
Portal p;
PortalExitSource() { this = p.getAnExitNode(true) }
override predicate isSourceFor(DataFlow::Configuration cfg, DataFlow::FlowLabel lbl) { any() }
/** Gets the portal of which this is an exit node. */
Portal getPortal() { result = p }
}

View File

@@ -1,91 +0,0 @@
/**
* Provides utility predicates for working with flow summary specifications.
*/
import javascript
/**
* Holds if `config` matches `spec`, that is, either `spec` is the ID of `config`
* or `spec` is the empty string and `config` is an arbitrary configuration.
*/
predicate configSpec(DataFlow::Configuration config, string spec) {
config.getId() = spec
or
spec = ""
}
/**
* Holds if `lbl` matches `spec`, that is, either `spec` is the name of `config`
* or `spec` is the empty string and `lbl` is the built-in flow label `data`.
*/
predicate sourceFlowLabelSpec(DataFlow::FlowLabel lbl, string spec) {
lbl.toString() = spec
or
spec = "" and
lbl.isData()
}
/**
* Holds if `lbl` matches `spec`, that is, either `spec` is the name of `config`
* or `spec` is the empty string and `lbl` is an arbitrary standard flow label.
*/
predicate sinkFlowLabelSpec(DataFlow::FlowLabel lbl, string spec) {
lbl.toString() = spec
or
spec = "" and
lbl.isDataOrTaint()
}
/**
* A comment that annotates data flow nodes as sources/sinks.
*
* Such a comment starts with "Semmle:", possibly preceded by whitespace, and
* may contain specifications of the form "source: label,config" and "sink: label, config"
* where again whitespace is not significant and the ",config" part may be missing.
*
* It applies to any data flow node that ends on the line where the comment starts,
* and annotates the node as being a source/sink with the given flow label(s) for
* the given configuration (or any configuration if omitted).
*/
class AnnotationComment extends Comment {
string ann;
AnnotationComment() { ann = getText().regexpCapture("(?s)\\s*Semmle:(.*)", 1) }
/**
* Holds if this comment applies to `nd`, that is, it starts on the same line on
* which `nd` ends.
*/
predicate appliesTo(DataFlow::Node nd) {
exists(string file, int line |
getLocation().hasLocationInfo(file, line, _, _, _) and
nd.hasLocationInfo(file, _, _, line, _)
)
}
/**
* Holds if this comment contains an annotation of the form `source: label, config`
* or `source: label` (modulo whitespace). In the latter case, `config` may be
* any configuration.
*/
predicate specifiesSource(DataFlow::FlowLabel label, DataFlow::Configuration config) {
exists(string spec |
spec = ann.regexpFind("(?<=\\bsource:)\\s*[^\\s,]+(\\s*,\\s*[^\\s,])?", _, _) and
sourceFlowLabelSpec(label, spec.splitAt(",", 0).trim()) and
configSpec(config, spec.splitAt(",", 1).trim())
)
}
/**
* Holds if this comment contains an annotation of the form `sink: label, config`
* or `sink: label` (modulo whitespace). In the latter case, `config` may be
* any configuration.
*/
predicate specifiesSink(string label, string config) {
exists(string spec |
spec = ann.regexpFind("(?<=\\bsink:)\\s*[^\\s,]+(\\s*,\\s*[^\\s,]+)?", _, _) and
sinkFlowLabelSpec(label, spec.splitAt(",", 0).trim()) and
configSpec(config, spec.splitAt(",", 1).trim())
)
}
}

View File

@@ -1,18 +0,0 @@
import javascript
import Shared
/**
* A data flow node that is annotated as a sink.
*/
class SinkFromAnnotation extends DataFlow::AdditionalSink {
AnnotationComment c;
SinkFromAnnotation() {
c.appliesTo(this) and
c.specifiesSink(_, _)
}
override predicate isSinkFor(DataFlow::Configuration cfg, DataFlow::FlowLabel lbl) {
c.specifiesSink(lbl, cfg)
}
}

View File

@@ -1,18 +0,0 @@
import javascript
import Shared
/**
* A data flow node that is annotated as a source.
*/
class SourceFromAnnotation extends DataFlow::AdditionalSource {
AnnotationComment c;
SourceFromAnnotation() {
c.appliesTo(this) and
c.specifiesSource(_, _)
}
override predicate isSourceFor(DataFlow::Configuration cfg, DataFlow::FlowLabel lbl) {
c.specifiesSource(lbl, cfg)
}
}