Skip to content

Commit

Permalink
Shared support for alert filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
cklin committed Aug 13, 2024
1 parent 4de0d10 commit a8cdf1f
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 0 deletions.
148 changes: 148 additions & 0 deletions shared/dataflow/codeql/dataflow/DataFlow.qll
Original file line number Diff line number Diff line change
Expand Up @@ -824,4 +824,152 @@ module DataFlowMake<LocationSig Location, InputSig<Location> Lang> {
}
}
}

/**
* This wrapper applies alert filters to an existing `ConfigSig` module. It is intended to be used
* in the specific case where both the dataflow sources and the dataflow sinks are presented in
* the query result, so we need to apply the alert filter on either the source or the sink (which
* is what this wrapper does).
*/
module FilteredConfig<ConfigSig Config> implements ConfigSig {
private import codeql.util.AlertFiltering

private module AlertFiltering = AlertFilteringImpl<Location>;

pragma[noinline]
private predicate hasFilteredSource() {
exists(Node n | Config::isSource(n) | AlertFiltering::filterByLocation(n.getLocation()))
}

pragma[noinline]
private predicate hasFilteredSink() {
exists(Node n | Config::isSink(n) | AlertFiltering::filterByLocation(n.getLocation()))
}

predicate isSource(Node source) {
Config::isSource(source) and
(
// If there are filtered sinks, we need to pass through all sources to preserve all alerts
// with filtered sinks. Otherwise the only alerts of interest are those with filtered
// sources, so we can perform the source filtering right here.
hasFilteredSink() or
AlertFiltering::filterByLocation(source.getLocation())
)
}

predicate isSink(Node sink) {
Config::isSink(sink) and
(
// If there are filtered sources, we need to pass through all sinks to preserve all alerts
// with filtered sources. Otherwise the only alerts of interest are those with filtered
// sinks, so we can perform the sink filtering right here.
hasFilteredSource() or
AlertFiltering::filterByLocation(sink.getLocation())
)
}

predicate isBarrier = Config::isBarrier/1;

predicate isBarrierIn = Config::isBarrierIn/1;

predicate isBarrierOut = Config::isBarrierOut/1;

predicate isAdditionalFlowStep = Config::isAdditionalFlowStep/2;

predicate allowImplicitRead = Config::allowImplicitRead/2;

predicate neverSkip = Config::neverSkip/1;

predicate fieldFlowBranchLimit = Config::fieldFlowBranchLimit/0;

predicate accessPathLimit = Config::accessPathLimit/0;

predicate getAFeature = Config::getAFeature/0;

predicate sourceGrouping = Config::sourceGrouping/2;

predicate sinkGrouping = Config::sinkGrouping/2;

predicate includeHiddenNodes = Config::includeHiddenNodes/0;
}

/**
* This wrapper applies alert filters to an existing `StateConfigSig` module. It is intended to be
* used in the specific case where both the dataflow sources and the dataflow sinks are present in
* the query result, so we need to apply the alert filter on either the source or the sink (which
* is what this wrapper does).
*/
module FilteredStateConfig<StateConfigSig Config> implements StateConfigSig {
private import codeql.util.AlertFiltering

private module AlertFiltering = AlertFilteringImpl<Location>;

class FlowState = Config::FlowState;

pragma[noinline]
private predicate hasFilteredSource() {
exists(Node n | Config::isSource(n, _) | AlertFiltering::filterByLocation(n.getLocation()))
}

pragma[noinline]
private predicate hasFilteredSink() {
exists(Node n |
Config::isSink(n, _) or
Config::isSink(n)
|
AlertFiltering::filterByLocation(n.getLocation())
)
}

predicate isSource(Node source, FlowState state) {
Config::isSource(source, state) and
(
// If there are filtered sinks, we need to pass through all sources to preserve all alerts
// with filtered sinks. Otherwise the only alerts of interest are those with filtered
// sources, so we can perform the source filtering right here.
hasFilteredSink() or
AlertFiltering::filterByLocation(source.getLocation())
)
}

predicate isSink(Node sink, FlowState state) {
Config::isSink(sink, state) and
(
// If there are filtered sources, we need to pass through all sinks to preserve all alerts
// with filtered sources. Otherwise the only alerts of interest are those with filtered
// sinks, so we can perform the sink filtering right here.
hasFilteredSource() or
AlertFiltering::filterByLocation(sink.getLocation())
)
}

predicate isSink(Node sink) {
Config::isSink(sink) and
(
// If there are filtered sources, we need to pass through all sinks to preserve all alerts
// with filtered sources. Otherwise the only alerts of interest are those with filtered
// sinks, so we can perform the sink filtering right here.
hasFilteredSource() or
AlertFiltering::filterByLocation(sink.getLocation())
)
}

predicate isBarrier = Config::isBarrier/1;

predicate isBarrier = Config::isBarrier/2;

predicate isBarrierIn = Config::isBarrierIn/1;

predicate isBarrierIn = Config::isBarrierIn/2;

predicate isBarrierOut = Config::isBarrierOut/1;

predicate isBarrierOut = Config::isBarrierOut/2;

predicate isAdditionalFlowStep = Config::isAdditionalFlowStep/2;

predicate isAdditionalFlowStep = Config::isAdditionalFlowStep/4;

predicate accessPathLimit = Config::accessPathLimit/0;
}
}
48 changes: 48 additions & 0 deletions shared/util/codeql/util/AlertFiltering.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Provides the `restrictAlertsTo` extensible predicate to restrict alerts to specific source
* locations, and the `AlertFilteringImpl` parameterized module to apply the filtering.
*/

private import codeql.util.Location

/**
* Restricts alerts to a specific location in specific files.
*
* If this predicate is empty, accept all alerts. Otherwise, accept alerts only at the specified
* locations. Note that alert restrictions apply only to the start line of an alert (even if the
* alert location spans multiple lines) because alerts are displayed on their start lines.
*
* - filePath: Absolute path of the file to restrict alerts to.
* - startLine: Start line number (starting with 1, inclusive) to restrict alerts to.
* - endLine: End line number (starting with 1, inclusive) to restrict alerts to.
*
* If startLine and endLine are both 0, accept alerts anywhere in the file.
*/
extensible predicate restrictAlertsTo(string filePath, int startLine, int endLine);

/** Module for applying alert location filtering. */
module AlertFilteringImpl<LocationSig Location> {
/** Applies alert filtering to the given location. */
bindingset[location]
predicate filterByLocation(Location location) {
not exists( | restrictAlertsTo(_, _, _))
or
exists(string filePath, int startLine, int endLine |
restrictAlertsTo(filePath, startLine, endLine) and
(
startLine = 0 and
endLine = 0 and
location.hasLocationInfo(filePath, _, _, _, _)
or
// Expand short ranges to join with the start line of the location
startLine <= endLine and
endLine - startLine <= 1000 and
location.hasLocationInfo(filePath, [startLine .. endLine], _, _, _)
or
// For long ranges, over-approximate with matching the entire file
endLine - startLine > 1000 and
location.hasLocationInfo(filePath, _, _, _, _)
)
)
}
}
7 changes: 7 additions & 0 deletions shared/util/ext/default-alert-filter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
extensions:

- addsTo:
pack: codeql/util
extensible: restrictAlertsTo
# Empty predicate means no restrictions on alert locations
data: []
2 changes: 2 additions & 0 deletions shared/util/qlpack.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ version: 1.0.6-dev
groups: shared
library: true
dependencies: null
dataExtensions:
- ext/*.yml
warnOnImplicitThis: true

0 comments on commit a8cdf1f

Please sign in to comment.