Skip to content

Commit 1c48bd5

Browse files
committed
Shared support for alert filtering
1 parent b001f47 commit 1c48bd5

File tree

4 files changed

+217
-0
lines changed

4 files changed

+217
-0
lines changed

shared/dataflow/codeql/dataflow/DataFlow.qll

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,9 @@ module Configs<LocationSig Location, InputSig<Location> Lang> {
437437
* is not visualized (as it is in a `path-problem` query).
438438
*/
439439
default predicate includeHiddenNodes() { none() }
440+
//
441+
// If you add a predicate to ConfigSig, please also add a corresponding
442+
// passthrough alias to FilteredConfig below.
440443
}
441444

442445
/** An input configuration for data flow using flow state. */
@@ -559,6 +562,9 @@ module Configs<LocationSig Location, InputSig<Location> Lang> {
559562
* is not visualized (as it is in a `path-problem` query).
560563
*/
561564
default predicate includeHiddenNodes() { none() }
565+
//
566+
// If you add a predicate to StateConfigSig, please also add a corresponding
567+
// passthrough alias to FilteredStateConfig below.
562568
}
563569
}
564570

@@ -835,4 +841,166 @@ module DataFlowMake<LocationSig Location, InputSig<Location> Lang> {
835841
}
836842
}
837843
}
844+
845+
/**
846+
* This wrapper applies alert filters to an existing `ConfigSig` module. It is intended to be used
847+
* in the specific case where both the dataflow sources and the dataflow sinks are presented in
848+
* the query result, so we need to apply the alert filter on either the source or the sink (which
849+
* is what this wrapper does).
850+
*/
851+
module FilteredConfig<ConfigSig Config> implements ConfigSig {
852+
private import codeql.util.AlertFiltering
853+
854+
private module AlertFiltering = AlertFilteringImpl<Location>;
855+
856+
pragma[noinline]
857+
private predicate hasFilteredSource() {
858+
exists(Node n | Config::isSource(n) | AlertFiltering::filterByLocation(n.getLocation()))
859+
}
860+
861+
pragma[noinline]
862+
private predicate hasFilteredSink() {
863+
exists(Node n | Config::isSink(n) | AlertFiltering::filterByLocation(n.getLocation()))
864+
}
865+
866+
predicate isSource(Node source) {
867+
Config::isSource(source) and
868+
(
869+
// If there are filtered sinks, we need to pass through all sources to preserve all alerts
870+
// with filtered sinks. Otherwise the only alerts of interest are those with filtered
871+
// sources, so we can perform the source filtering right here.
872+
hasFilteredSink() or
873+
AlertFiltering::filterByLocation(source.getLocation())
874+
)
875+
}
876+
877+
predicate isSink(Node sink) {
878+
Config::isSink(sink) and
879+
(
880+
// If there are filtered sources, we need to pass through all sinks to preserve all alerts
881+
// with filtered sources. Otherwise the only alerts of interest are those with filtered
882+
// sinks, so we can perform the sink filtering right here.
883+
hasFilteredSource() or
884+
AlertFiltering::filterByLocation(sink.getLocation())
885+
)
886+
}
887+
888+
predicate isBarrier = Config::isBarrier/1;
889+
890+
predicate isBarrierIn = Config::isBarrierIn/1;
891+
892+
predicate isBarrierOut = Config::isBarrierOut/1;
893+
894+
predicate isAdditionalFlowStep = Config::isAdditionalFlowStep/2;
895+
896+
predicate allowImplicitRead = Config::allowImplicitRead/2;
897+
898+
predicate neverSkip = Config::neverSkip/1;
899+
900+
predicate fieldFlowBranchLimit = Config::fieldFlowBranchLimit/0;
901+
902+
predicate accessPathLimit = Config::accessPathLimit/0;
903+
904+
predicate getAFeature = Config::getAFeature/0;
905+
906+
predicate sourceGrouping = Config::sourceGrouping/2;
907+
908+
predicate sinkGrouping = Config::sinkGrouping/2;
909+
910+
predicate includeHiddenNodes = Config::includeHiddenNodes/0;
911+
}
912+
913+
/**
914+
* This wrapper applies alert filters to an existing `StateConfigSig` module. It is intended to be
915+
* used in the specific case where both the dataflow sources and the dataflow sinks are present in
916+
* the query result, so we need to apply the alert filter on either the source or the sink (which
917+
* is what this wrapper does).
918+
*/
919+
module FilteredStateConfig<StateConfigSig Config> implements StateConfigSig {
920+
private import codeql.util.AlertFiltering
921+
922+
private module AlertFiltering = AlertFilteringImpl<Location>;
923+
924+
class FlowState = Config::FlowState;
925+
926+
pragma[noinline]
927+
private predicate hasFilteredSource() {
928+
exists(Node n | Config::isSource(n, _) | AlertFiltering::filterByLocation(n.getLocation()))
929+
}
930+
931+
pragma[noinline]
932+
private predicate hasFilteredSink() {
933+
exists(Node n |
934+
Config::isSink(n, _) or
935+
Config::isSink(n)
936+
|
937+
AlertFiltering::filterByLocation(n.getLocation())
938+
)
939+
}
940+
941+
predicate isSource(Node source, FlowState state) {
942+
Config::isSource(source, state) and
943+
(
944+
// If there are filtered sinks, we need to pass through all sources to preserve all alerts
945+
// with filtered sinks. Otherwise the only alerts of interest are those with filtered
946+
// sources, so we can perform the source filtering right here.
947+
hasFilteredSink() or
948+
AlertFiltering::filterByLocation(source.getLocation())
949+
)
950+
}
951+
952+
predicate isSink(Node sink, FlowState state) {
953+
Config::isSink(sink, state) and
954+
(
955+
// If there are filtered sources, we need to pass through all sinks to preserve all alerts
956+
// with filtered sources. Otherwise the only alerts of interest are those with filtered
957+
// sinks, so we can perform the sink filtering right here.
958+
hasFilteredSource() or
959+
AlertFiltering::filterByLocation(sink.getLocation())
960+
)
961+
}
962+
963+
predicate isSink(Node sink) {
964+
Config::isSink(sink) and
965+
(
966+
// If there are filtered sources, we need to pass through all sinks to preserve all alerts
967+
// with filtered sources. Otherwise the only alerts of interest are those with filtered
968+
// sinks, so we can perform the sink filtering right here.
969+
hasFilteredSource() or
970+
AlertFiltering::filterByLocation(sink.getLocation())
971+
)
972+
}
973+
974+
predicate isBarrier = Config::isBarrier/1;
975+
976+
predicate isBarrier = Config::isBarrier/2;
977+
978+
predicate isBarrierIn = Config::isBarrierIn/1;
979+
980+
predicate isBarrierIn = Config::isBarrierIn/2;
981+
982+
predicate isBarrierOut = Config::isBarrierOut/1;
983+
984+
predicate isBarrierOut = Config::isBarrierOut/2;
985+
986+
predicate isAdditionalFlowStep = Config::isAdditionalFlowStep/2;
987+
988+
predicate isAdditionalFlowStep = Config::isAdditionalFlowStep/4;
989+
990+
predicate allowImplicitRead = Config::allowImplicitRead/2;
991+
992+
predicate neverSkip = Config::neverSkip/1;
993+
994+
predicate fieldFlowBranchLimit = Config::fieldFlowBranchLimit/0;
995+
996+
predicate accessPathLimit = Config::accessPathLimit/0;
997+
998+
predicate getAFeature = Config::getAFeature/0;
999+
1000+
predicate sourceGrouping = Config::sourceGrouping/2;
1001+
1002+
predicate sinkGrouping = Config::sinkGrouping/2;
1003+
1004+
predicate includeHiddenNodes = Config::includeHiddenNodes/0;
1005+
}
8381006
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Provides the `restrictAlertsTo` extensible predicate to restrict alerts to specific source
3+
* locations, and the `AlertFilteringImpl` parameterized module to apply the filtering.
4+
*/
5+
6+
private import codeql.util.Location
7+
8+
/**
9+
* Restricts alerts to a specific location in specific files.
10+
*
11+
* If this predicate is empty, accept all alerts. Otherwise, accept alerts only at the specified
12+
* locations. Note that alert restrictions apply only to the start line of an alert (even if the
13+
* alert location spans multiple lines) because alerts are displayed on their start lines.
14+
*
15+
* - filePath: Absolute path of the file to restrict alerts to.
16+
* - startLine: Start line number (starting with 1, inclusive) to restrict alerts to.
17+
* - endLine: End line number (starting with 1, inclusive) to restrict alerts to.
18+
*
19+
* If startLine and endLine are both 0, accept alerts anywhere in the file.
20+
*/
21+
extensible predicate restrictAlertsTo(string filePath, int startLine, int endLine);
22+
23+
/** Module for applying alert location filtering. */
24+
module AlertFilteringImpl<LocationSig Location> {
25+
/** Applies alert filtering to the given location. */
26+
bindingset[location]
27+
predicate filterByLocation(Location location) {
28+
not restrictAlertsTo(_, _, _)
29+
or
30+
exists(string filePath, int startLine, int endLine |
31+
restrictAlertsTo(filePath, startLine, endLine)
32+
|
33+
startLine = 0 and
34+
endLine = 0 and
35+
location.hasLocationInfo(filePath, _, _, _, _)
36+
or
37+
location.hasLocationInfo(filePath, [startLine .. endLine], _, _, _)
38+
)
39+
}
40+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
extensions:
2+
3+
- addsTo:
4+
pack: codeql/util
5+
extensible: restrictAlertsTo
6+
# Empty predicate means no restrictions on alert locations
7+
data: []

shared/util/qlpack.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@ version: 1.0.6-dev
33
groups: shared
44
library: true
55
dependencies: null
6+
dataExtensions:
7+
- ext/*.yml
68
warnOnImplicitThis: true

0 commit comments

Comments
 (0)