Skip to content

Diff-informed queries via primary/secondary abstractions #19586

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions java/ql/lib/semmle/code/java/security/XSS.qll
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ private class DefaultXssSink extends XssSink {
DefaultXssSink() {
sinkNode(this, ["html-injection", "js-injection"])
or
exists(MethodCall ma |
ma.getMethod() instanceof WritingMethod and
XssVulnerableWriterSourceToWritingMethodFlow::flowToExpr(ma.getQualifier()) and
this.asExpr() = ma.getArgument(_)
exists(DataFlow::Node n |
XssVulnerableWriterSourceToWritingMethodFlow::flowTo(n) and
XssVulnerableWriterSourceToWritingMethodFlowSecondaryConfig::getPrimaryOfSecondaryNode(_, n) =
this
)
}
}
Expand All @@ -73,8 +73,24 @@ private module XssVulnerableWriterSourceToWritingMethodFlowConfig implements Dat
}
}

private module XssVulnerableWriterSourceToWritingMethodFlowSecondaryConfig implements
DataFlow::SecondaryConfig
{
DataFlow::Node getPrimaryOfSecondaryNode(
DataFlow::IsSourceOrSink sourceOrSink, DataFlow::Node sink
) {
sourceOrSink instanceof DataFlow::IsSink and
exists(MethodCall ma |
XssVulnerableWriterSourceToWritingMethodFlowConfig::isSink(sink) and
sink.asExpr() = ma.getQualifier() and
result.asExpr() = ma.getAnArgument()
)
}
}

private module XssVulnerableWriterSourceToWritingMethodFlow =
TaintTracking::Global<XssVulnerableWriterSourceToWritingMethodFlowConfig>;
TaintTracking::FindSinks<XssVulnerableWriterSourceToWritingMethodFlowConfig,
XssVulnerableWriterSourceToWritingMethodFlowSecondaryConfig>;

/** A method that can be used to output data to an output stream or writer. */
private class WritingMethod extends Method {
Expand Down
4 changes: 1 addition & 3 deletions java/ql/lib/semmle/code/java/security/XssQuery.qll
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ module XssConfig implements DataFlow::ConfigSig {
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
any(XssAdditionalTaintStep s).step(node1, node2)
}

predicate observeDiffInformedIncrementalMode() { any() }
}

/** Tracks flow from remote sources to cross site scripting vulnerabilities. */
module XssFlow = TaintTracking::Global<XssConfig>;
module XssFlow = TaintTracking::Primary<XssConfig>;
10 changes: 10 additions & 0 deletions java/ql/src/Security/CWE/CWE-079/XSS.ql
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@
import semmle.code.java.security.XssQuery
import XssFlow::PathGraph

class IsDiffInformed extends DataFlow::DiffInformedQuery {
// This predicate is overridden to be more precise than the default
// implementation in order to support secondary secondary data-flow

Check warning

Code scanning / CodeQL

Comment has repeated word Warning

The comment repeats secondary.
// configurations that find sinks.
override Location getASelectedSourceLocation(DataFlow::Node source) {
XssConfig::isSource(source) and
result = source.getLocation()
}
}

from XssFlow::PathNode source, XssFlow::PathNode sink
where XssFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "Cross-site scripting vulnerability due to a $@.",
Expand Down
31 changes: 31 additions & 0 deletions shared/dataflow/codeql/dataflow/DataFlow.qll
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,10 @@
predicate flowToExpr(DataFlowExpr sink);
}

class DiffInformedQuery = DiffInformedQueryImpl;

import SecondaryConfigHelpers

/**
* Constructs a global data flow computation.
*/
Expand Down Expand Up @@ -742,6 +746,33 @@
import Flow
}

module Primary<ConfigSig Config> implements GlobalFlowSig {
private module Config0 implements FullStateConfigSig {

Check warning

Code scanning / CodeQL

Data flow configuration module naming Warning

Modules implementing a data flow configuration should end in Config.
import DefaultState<Config>
import Config

predicate accessPathLimit = Config::accessPathLimit/0;

predicate isAdditionalFlowStep(Node node1, Node node2, string model) {
Config::isAdditionalFlowStep(node1, node2) and model = "Config"
}
}

private module C implements FullStateConfigSig {

Check warning

Code scanning / CodeQL

Data flow configuration module naming Warning

Modules implementing a data flow configuration should end in Config.
import MakePrimaryDiffInformed<Config0>

predicate accessPathLimit = Config0::accessPathLimit/0;
}

private module Stage1 = ImplStage1<C>;

import Stage1::PartialFlow

private module Flow = Impl<C, Stage1::Stage1NoState>;

import Flow
}

signature class PathNodeSig {
/** Gets a textual representation of this element. */
string toString();
Expand Down
55 changes: 55 additions & 0 deletions shared/dataflow/codeql/dataflow/TaintTracking.qll
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,61 @@
import Flow
}

/**
* Constructs a global taint tracking computation.
*/
module Primary<DataFlow::ConfigSig Config> implements DataFlow::GlobalFlowSig {
private module Config0 implements DataFlowInternal::FullStateConfigSig {
import DataFlowInternal::DefaultState<Config>
import Config

predicate isAdditionalFlowStep(
DataFlowLang::Node node1, DataFlowLang::Node node2, string model
) {
Config::isAdditionalFlowStep(node1, node2) and model = "Config"
}
}

private module C implements DataFlowInternal::FullStateConfigSig {

Check warning

Code scanning / CodeQL

Data flow configuration module naming Warning

Modules implementing a data flow configuration should end in Config.
import DataFlowInternal::MakePrimaryDiffInformed<AddTaintDefaults<Config0>>
}

private module Stage1 = DataFlowInternalStage1::ImplStage1<C>;

import Stage1::PartialFlow

private module Flow = DataFlowInternal::Impl<C, Stage1::Stage1NoState>;

import Flow
}

module FindSinks<DataFlow::ConfigSig Config, DataFlow::SecondaryConfig SC> implements
DataFlow::GlobalFlowSig
{
private module Config0 implements DataFlowInternal::FullStateConfigSig {

Check warning

Code scanning / CodeQL

Data flow configuration module naming Warning

Modules implementing a data flow configuration should end in Config.
import DataFlowInternal::DefaultState<Config>
import Config

predicate isAdditionalFlowStep(
DataFlowLang::Node node1, DataFlowLang::Node node2, string model
) {
Config::isAdditionalFlowStep(node1, node2) and model = "Config"
}
}

private module C implements DataFlowInternal::FullStateConfigSig {

Check warning

Code scanning / CodeQL

Data flow configuration module naming Warning

Modules implementing a data flow configuration should end in Config.
import DataFlowInternal::MakeSinkFinder<AddTaintDefaults<Config0>, SC>
}

private module Stage1 = DataFlowInternalStage1::ImplStage1<C>;

import Stage1::PartialFlow

private module Flow = DataFlowInternal::Impl<C, Stage1::Stage1NoState>;

import Flow
}

signature int speculationLimitSig();

private module AddSpeculativeTaintSteps<
Expand All @@ -166,7 +221,7 @@

class FlowState extends TFlowState {
private Config::FlowState state;
private int spec;

Check warning

Code scanning / CodeQL

Data flow configuration module naming Warning

Modules implementing a data flow configuration should end in Config.

FlowState() { this = TMkFlowState(state, spec) }

Expand Down
114 changes: 114 additions & 0 deletions shared/dataflow/codeql/dataflow/internal/DataFlowImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ module MakeImpl<LocationSig Location, InputSig<Location> Lang> {
private import DataFlowImplCommon::MakeImplCommon<Location, Lang>
private import DataFlowImplCommonPublic
private import Aliases
private import codeql.util.AlertFiltering

private module AlertFiltering = AlertFilteringImpl<Location>;

/**
* An input configuration for data flow using flow state. This signature equals
Expand Down Expand Up @@ -169,6 +172,117 @@ module MakeImpl<LocationSig Location, InputSig<Location> Lang> {
}
}

/**
* This private type is used instead of `Unit` to ensure that users of this
* library don't take an accidental dependency on convertability to `Unit`,
* allowing us to change the implementation in the future.
*/
private newtype TDiffInformedBase = MkDiffInformedBase()

abstract class DiffInformedQueryImpl extends TDiffInformedBase {
DiffInformedQueryImpl() { any() }

string toString() { result = "DataFlow::DiffInformedQuery" }

Location getASelectedSourceLocation(Node source) { result = source.getLocation() }

Location getASelectedSinkLocation(Node sink) { result = sink.getLocation() }

pragma[nomagic]
predicate hasSourceInDiffRange() {
AlertFiltering::filterByLocation(this.getASelectedSourceLocation(_))
}

pragma[nomagic]
predicate hasSinkInDiffRange() {
AlertFiltering::filterByLocation(this.getASelectedSinkLocation(_))
}
}

module MakePrimaryDiffInformed<FullStateConfigSig Config> implements FullStateConfigSig {
// Workaround for name clash
predicate accessPathLimit = Config::accessPathLimit/0;

import Config

predicate observeDiffInformedIncrementalMode() {
// Add to existing configuration to support composition of config transformers
Config::observeDiffInformedIncrementalMode()
or
exists(DiffInformedQueryImpl q)
}

Location getASelectedSourceLocation(Node source) {
result = Config::getASelectedSourceLocation(source)
or
exists(DiffInformedQueryImpl q | result = q.getASelectedSourceLocation(source))
}

Location getASelectedSinkLocation(Node sink) {
result = Config::getASelectedSinkLocation(sink)
or
exists(DiffInformedQueryImpl q | result = q.getASelectedSinkLocation(sink))
}
}

module SecondaryConfigHelpers {
newtype IsSourceOrSink =
IsSource() or
IsSink()

signature module SecondaryConfig {
/**
* Gets the source/sink node from the primary configuration that is
* informed by a given source/sink node from the secondary configuration.
* Whether the secondary node is a source or a sink is determined by
* `sourceOrSink`.
*/
bindingset[sourceOrSink, secondaryNode]
Node getPrimaryOfSecondaryNode(IsSourceOrSink sourceOrSink, Node secondaryNode);
}
}

module MakeSinkFinder<FullStateConfigSig Config, SecondaryConfig SC> implements FullStateConfigSig
{
// Workaround for name clash
predicate accessPathLimit = Config::accessPathLimit/0;

import Config

predicate observeDiffInformedIncrementalMode() {
// Add to existing configuration to support composition of config transformers
Config::observeDiffInformedIncrementalMode()
or
// Because this configuration is for finding sinks to be used in a main
// data-flow configuration, this configuration should only restrict the
// sinks to be found if there are no main-configuration sources in the
// diff range. That's because if there is such a source, we need to
// report query results for it even with sinks outside the diff range.
//
// The `MakeSinkFinder` and `MakeSourceFinder` modules are separate
// because each can only call one of `hasSourceInDiffRange` or
// `hasSinkInDiffRange`. Otherwise it would look like a non-monotonic
// recursion.
exists(DiffInformedQuery q | not q.hasSourceInDiffRange())
}

Location getASelectedSourceLocation(Node source) {
result = Config::getASelectedSourceLocation(source)
or
exists(DiffInformedQueryImpl q, IsSource s |
result = q.getASelectedSourceLocation(SC::getPrimaryOfSecondaryNode(s, source))
)
}

Location getASelectedSinkLocation(Node sink) {
result = Config::getASelectedSinkLocation(sink)
or
exists(DiffInformedQueryImpl q, IsSink s |
result = q.getASelectedSinkLocation(SC::getPrimaryOfSecondaryNode(s, sink))
)
}
}

/**
* Constructs a data flow computation given a full input configuration, and
* an initial stage 1 pruning.
Expand Down
Loading